<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CEQCQn49cSp7ImA9WhRQEUo.&quot;"><id>tag:blogger.com,1999:blog-6958306</id><updated>2011-12-06T06:52:43.069-05:00</updated><category term="Woot" /><category term="sonar" /><category term="TestNG" /><category term="enterprise wiki" /><category term="firebug" /><category term="maven" /><category term="selenium" /><category term="page objects" /><category term="linkedin" /><category term="division iii" /><category term="rockabye baby" /><category term="d iii" /><category term="SAP" /><category term="crew" /><category term="bill duff" /><category term="KM" /><category term="plaxo" /><category term="PC" /><category term="joelonsoftware" /><category term="Flex" /><category term="email" /><category term="David Seah" /><category term="BoIB" /><category term="greasemonkey" /><category term="facebook" /><category term="pulse" /><category term="java code-retreat prison" /><category term="jQuery" /><category term="XSLT" /><category term="SAP Portal" /><category term="CSS" /><category term="PDF" /><category term="Javascript" /><category term="Hungarian Algorithm" /><category term="AIR" /><category term="trophymanager" /><category term="airlines" /><category term="FSN HD" /><category term="jasmine" /><category term="parody" /><category term="geek" /><category term="TWC" /><category term="case western reserve" /><category term="inbox zero" /><category term="case" /><category term="Google Analytics" /><category term="TrExMa" /><category term="Free HD" /><category term="book review" /><category term="marketing" /><category term="customer care" /><category term="waterfall" /><category term="enterplicity" /><category term="you tube" /><category term="widget" /><category term="US Mexico" /><category term="all star game" /><category term="blue jackets" /><category term="hilliard" /><category term="Bill Brownson" /><category term="winstone" /><category term="Time Warner Cable" /><category term="enterprise blog" /><category term="metallica" /><category term="legacy" /><category term="destroyers" /><category term="OpenSocial" /><category term="mls" /><category term="AFP" /><category term="US Soccer" /><category term="sabotage" /><category term="punisher" /><category term="agile" /><category term="Cucumber" /><category term="watir-webdriver" /><category term="Woot-Off" /><category term="productivity" /><category term="football" /><category term="site-deploy" /><category term="screenshots" /><category term="celerity" /><category term="gtd" /><category term="lean" /><category term="ETT" /><category term="zune" /><category term="AIR Proxy" /><category term="Vs/Golf HD" /><category term="hudson" /><category term="human weapon" /><category term="outlook" /><category term="Delta" /><category term="HtmlUnit" /><category term="qa" /><category term="Ruby" /><category term="page parts" /><category term="Columbus City Council" /><category term="playoffs" /><category term="Spry" /><category term="decorator pattern" /><category term="lost phone" /><category term="baggage" /><title>Two Four One</title><subtitle type="html">Semi-Random thoughts on life, computers, sports, and what not....but mostly programming.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://twofourone.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>298</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/TwoFourOne" /><feedburner:info uri="twofourone" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;D0AHRXg4fyp7ImA9WhdaEk0.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-1156808889669539134</id><published>2011-10-21T10:01:00.000-04:00</published><updated>2011-10-21T10:02:14.637-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-21T10:02:14.637-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Cucumber" /><category scheme="http://www.blogger.com/atom/ns#" term="watir-webdriver" /><category scheme="http://www.blogger.com/atom/ns#" term="selenium" /><title>Monkey Patching Firefox Race Conditions For Watir-WebDriver/Cucumber</title><content type="html">If your team is like my team, you're trying to find ways to automate everything.  Perhaps you're also using watir-webdriver to run browser based tests with Cucumber.  This works great locally, but when you try to run your tests on a CI environment you start to run into some very strange problems with Firefox testing. &amp;nbsp;You start to get errors around not being able to find the h1 tag on your page. &amp;nbsp;You look at the screen shot you've generated using a method similar to &lt;a href="http://twofourone.blogspot.com/2011/04/making-your-cukes-more-awesomer-with.html"&gt;this&lt;/a&gt;, and you find that your screenshot shows the text that contains the h1. &amp;nbsp;In fact, your error dumped html file also shows the h1 tag:&lt;br /&gt;
&lt;script src="https://gist.github.com/1303885.js?file=Selenium%20Issue%202099%20StackTrace.txt"&gt;&lt;/script&gt;
&lt;br /&gt;
You've run into &lt;a href="http://code.google.com/p/selenium/issues/detail?id=2099"&gt;selenium issue 2099&lt;/a&gt;. &amp;nbsp;It appears what happens with this error is that the test is executing before the page has fully loaded, causing it to fail. &amp;nbsp;Old school race conditions. &amp;nbsp;Nice. &lt;br /&gt;
&lt;br /&gt;
So how do we get around this? &amp;nbsp;There's been a few postings on how to handle this with &lt;a href="https://gist.github.com/1137135"&gt;Capybara&lt;/a&gt;, but what about tools that use selenium-webdriver more directly? &amp;nbsp;Here's a monkey patch that hacks over the Ruby bindings to enable tools like watir-webdriver to retry. &amp;nbsp;This patch will retry the find until selenium-webdriver receives an error that is not related to the strange InvalidSelectorError that it gets when the Firefox isn't done loading the page.  I just stuff this file into the support directory in a Cucumber suite.&lt;br /&gt;
&lt;script src="https://gist.github.com/1303885.js?file=patch_firefox_timeouts.rb"&gt;&lt;/script&gt;
&lt;br /&gt;
Since this is hacky and a monkey patch, I'm very keen on ideas on how to improve this implementation, since my Ruby skills are very raw.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-1156808889669539134?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/vuqOdmd5ImFGfGz0hSTInfSfGqc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/vuqOdmd5ImFGfGz0hSTInfSfGqc/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/vuqOdmd5ImFGfGz0hSTInfSfGqc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/vuqOdmd5ImFGfGz0hSTInfSfGqc/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=WnIi7wh3MF8:QVyJhJid8GU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=WnIi7wh3MF8:QVyJhJid8GU:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=WnIi7wh3MF8:QVyJhJid8GU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=WnIi7wh3MF8:QVyJhJid8GU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=WnIi7wh3MF8:QVyJhJid8GU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=WnIi7wh3MF8:QVyJhJid8GU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=WnIi7wh3MF8:QVyJhJid8GU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=WnIi7wh3MF8:QVyJhJid8GU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/WnIi7wh3MF8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/1156808889669539134/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=1156808889669539134" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/1156808889669539134?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/1156808889669539134?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/WnIi7wh3MF8/monkey-patching-selenium-issue-2099-for.html" title="Monkey Patching Firefox Race Conditions For Watir-WebDriver/Cucumber" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2011/10/monkey-patching-selenium-issue-2099-for.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEUGSHo8eip7ImA9WhdVF0w.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-14956257584171167</id><published>2011-09-22T14:30:00.000-04:00</published><updated>2011-09-22T14:30:29.472-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-22T14:30:29.472-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="legacy" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><category scheme="http://www.blogger.com/atom/ns#" term="jasmine" /><title>Integrating jasmine-maven-plugin With Your Legacy Enterprisey App</title><content type="html">Let's say you're working on enhancing an old legacy app with javascript.  Doesn't really matter what sort of app, just legacy, with an old source tree and an old deployment style.  How can you integrate jasmine within that app and have it also integrate with your new fangled CI environment?  What if you've also been asked to use maven since it's "The way forward" for your organization? How can you accomplish this?&lt;br /&gt;
&lt;br /&gt;
Well, there's the &lt;a href="http://searls.github.com/jasmine-maven-plugin/"&gt;jasmine-maven-plugin&lt;/a&gt;, which is all sorts of awesome, but because you have a legacy file structure there's no clear way on how to hook it into your legacy app.  But when there's a will there's a way. &lt;br /&gt;
&lt;br /&gt;
There are several problems that need to be tackled here.  Let's go over them bit by bit.&lt;br /&gt;
&lt;br /&gt;
First, how do we get jasmine-maven-plugin to see our legacy app?&lt;br /&gt;
&lt;br /&gt;
Let's hope that you can create your project somewhere "near" the legacy app in the SCM system.  In this example, I'm going to assume that we have a tree structure that looks like this:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1235433.js?file=file-structure.md"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
Pretty simple, we'll put our specs inside jasmine-tests\src\test\javascript.  We'll put any jasmine required javascript inside jasmine-tests\src\test\javascript\lib.  We just need to figure out how to tell jasmine-maven-plugin to look at our legacy app for our scripts. Fortunately, it's configurable.  Let's look at a snippet of the pom.xml that shows how you would configure the plugin.&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1235433.js?file=configure-jasmine-maven-plugin-pom-snippet.xml"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
In this configuration, we're telling the jasmine-maven-plugin to look outside of our project structure for our javascript files.  It even preloads our new fangled jquery thing that we've introduced into our legacy application from our legacy.js.src property.  The key to this is that we've kept the configuration of the plugin &lt;strong&gt;outside&lt;/strong&gt; of our execution configuration.  When jasmine-maven-plugin runs, maven will actually fork a process to generate the manual runner.  It will not receive the configuration settings if you're using an older maven like 2.0.11.  I do not know if 2.2.1 or 3.x change this.  By placing the configuration at the top level of the plugin, we fix that concern.&lt;br /&gt;
&lt;br /&gt;
At this point, we actually have a plugin that can execute on our system if we've checked out our entire source tree.  It should also execute in your CI environment.  &lt;br /&gt;
&lt;br /&gt;
However, some teams, like to check things out in different directory structures than what's in the SCM tree due to their tools (like Eclipse).  Although your team is excited, they've also noticed that the ManualTestRunner is deprecated.  When they try to run jasmine:bdd, they find that nothing gets picked up from the legacy app because the jetty runs from the root of your jasmine-tests directory.  It can't see things underneath it.  Normally you'd create a symbolic link, but...you're on Windows.  Windows XP. Remember, I said &lt;em&gt;Enterprisey&lt;/em&gt; app, not just legacy.&lt;br /&gt;
&lt;br /&gt;
How can you fix the process so you can run jasmine:bdd like good sensible developers? Glad you asked. Maven profiles to the rescue. And, a neat plugin that lets you build symbolic links on crazy things like Windows XP. &lt;br /&gt;
&lt;br /&gt;
Let's look at the profile:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1235433.js?file=profile-pom-snippet.xml"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
The &lt;a href="http://pyx4j.com/pyx4j-maven-plugins/maven-junction-plugin/introduction.html"&gt;maven-junction-plugin&lt;/a&gt; actually builds symbolic links in both unix and windows.  We redefine the location of our legacy.js.src to be a temporary directory inside the jasmine-tests project.  The checkedout.legacy.js.src points to where our application has been checked out locally.  The plugin is executed in the initialize phase so it occurs before jasmine.  And initialize is supposed to setup directory structures, and the like, so it makes sense.&lt;br /&gt;
&lt;br /&gt;
Since we've redefined the legacy.js.src, jasmine:bdd will simply see our files as if they are inside the project, and the jetty will be able to serve them properly.&lt;br /&gt;
&lt;br /&gt;
Our final pom looks like this:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1235433.js?file=pom.xml"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
Hopefully this is helpful for you.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-14956257584171167?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/rxVwWhScnPYo7xKby-7c5zT4E2M/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/rxVwWhScnPYo7xKby-7c5zT4E2M/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/rxVwWhScnPYo7xKby-7c5zT4E2M/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/rxVwWhScnPYo7xKby-7c5zT4E2M/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=VxZ4eE9n8dI:5D5jGZOMycY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=VxZ4eE9n8dI:5D5jGZOMycY:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=VxZ4eE9n8dI:5D5jGZOMycY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=VxZ4eE9n8dI:5D5jGZOMycY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=VxZ4eE9n8dI:5D5jGZOMycY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=VxZ4eE9n8dI:5D5jGZOMycY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=VxZ4eE9n8dI:5D5jGZOMycY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=VxZ4eE9n8dI:5D5jGZOMycY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/VxZ4eE9n8dI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/14956257584171167/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=14956257584171167" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/14956257584171167?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/14956257584171167?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/VxZ4eE9n8dI/integrating-jasmine-maven-plugin-with.html" title="Integrating jasmine-maven-plugin With Your Legacy Enterprisey App" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2011/09/integrating-jasmine-maven-plugin-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMFQHk_eip7ImA9WhdREkw.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-3363371006344809893</id><published>2011-08-01T11:55:00.001-04:00</published><updated>2011-08-01T12:00:11.742-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-01T12:00:11.742-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="page objects" /><category scheme="http://www.blogger.com/atom/ns#" term="watir-webdriver" /><category scheme="http://www.blogger.com/atom/ns#" term="page parts" /><title>The Problem with PageObjects, why PageParts are needed</title><content type="html">Over the past year or so, several big dogs in the automated web testing community have been pushing hard with the concept of PageObjects.  This is a great thing.  As such we now have at least two different page object frameworks to use with Watir-Webdriver thanks to the efforts of &lt;a href="https://github.com/alisterscott/watir-page-helper"&gt;Alister Scott&lt;/a&gt; and &lt;a href="https://github.com/cheezy/page-object"&gt;Cheezy&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
However, the page object frameworks have placed an item on the backburner.  I'm not sure why it is, but the concept of repeating items inside of a page has been ignored at this point.  Let's say you have a website like &lt;a href="http://finance.google.com/finance"&gt;Google Finance&lt;/a&gt;. The list of news items is a great example of a what I'm calling a PagePart.  &lt;br /&gt;
&lt;br /&gt;
PageParts are items that repeat themselves on a page in terms of content types.  They repeat with different content depending upon the user who is logged in, but they all look the same semantically.  Here's what a Google Finance News item looks like: &lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1118208.js?file=Google%20Finance%20News%20Cluster.html"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
There are four of these items wrapped in a div with market-news-stream as it's id.&lt;br /&gt;
&lt;br /&gt;
Without PageParts you might do this:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1118208.js?file=google_finance_no_page_part.rb"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
So how do we identify these items in the tests?  When I first started tackling this sort of problem on my current project I had pass through methods inside my PageObject that went to a simple PagePart.  This worked, but didn't give me the cool features of the PageObject frameworks, and wasn't that the whole point of adopting the framework?  &lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1118208.js?file=google_finance_pass_thru.rb"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;script src="https://gist.github.com/1118208.js?file=google_finance_part_pass_thru.rb"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
Wow, this is more complex than not using page parts.&lt;br /&gt;
&lt;br /&gt;
So, how exactly is this better?  It's better when you convert your page parts to use the PageObject framework.  Caveat, I do not know how you would do this in Cheezy's PageObjects gem, but it works just fine in the watir-page-helper gem:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1118208.js?file=base_part.rb"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
Every PagePart extends the BasePart.  You'll select your container div in your PagePart object using the part_div method.  Obviously this could be any HTML element, but it's most likely to be a div.  Since the BasePart will redefine @browser with the div, we'll be able to use the cool shorthand, rather than define every method we might need:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/1118208.js?file=google_finance_part.rb"&gt;&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
The example may seem a bit contrived, but, maybe you're not working with an interface as clean as Google Finance.  If you are, you might get some benefit out of using PageParts.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-3363371006344809893?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/2HQ1HmREoxweTV6WrIKWLUlUnbo/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/2HQ1HmREoxweTV6WrIKWLUlUnbo/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/2HQ1HmREoxweTV6WrIKWLUlUnbo/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/2HQ1HmREoxweTV6WrIKWLUlUnbo/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=SgxfwMeFxaU:ECXKqnh_kzs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=SgxfwMeFxaU:ECXKqnh_kzs:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=SgxfwMeFxaU:ECXKqnh_kzs:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=SgxfwMeFxaU:ECXKqnh_kzs:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=SgxfwMeFxaU:ECXKqnh_kzs:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=SgxfwMeFxaU:ECXKqnh_kzs:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=SgxfwMeFxaU:ECXKqnh_kzs:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=SgxfwMeFxaU:ECXKqnh_kzs:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/SgxfwMeFxaU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/3363371006344809893/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=3363371006344809893" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/3363371006344809893?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/3363371006344809893?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/SgxfwMeFxaU/problem-with-pageobjects-why-pageparts.html" title="The Problem with PageObjects, why PageParts are needed" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>3</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2011/08/problem-with-pageobjects-why-pageparts.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkIDQHYzfip7ImA9WhZRF00.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-8395561670983667794</id><published>2011-04-13T11:29:00.001-04:00</published><updated>2011-04-13T11:29:31.886-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-13T11:29:31.886-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Cucumber" /><category scheme="http://www.blogger.com/atom/ns#" term="screenshots" /><category scheme="http://www.blogger.com/atom/ns#" term="watir-webdriver" /><title>Making Your Cukes More Awesomer With Screenshots</title><content type="html">In my last post about &lt;a href="http://twofourone.blogspot.com/2011/04/cucumber-with-celerity-and-watir.html"&gt;Cucumber&lt;/a&gt;, I alluded to using &lt;a href="https://github.com/jarib/watir-webdriver"&gt;watir-webdriver&lt;/a&gt; instead of using native &lt;a href="http://watir.com/"&gt;watir&lt;/a&gt;.  There was one very surprising advantage I discovered with watir-webdriver's use of &lt;a href="http://code.google.com/p/selenium/"&gt;selenium&lt;/a&gt; as a backend: Awesome Screenshot API.&lt;br /&gt;
&lt;br /&gt;
Here's how we setup our Cucumber suite to take screenshots (and html/txt "screenshots") using watir-webdriver and celerity. &amp;nbsp;First, set your tests up to write an error when a test fails:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/917632.js?file=hooks.rb"&gt;
&lt;/script&gt;&lt;br /&gt;
In this hook, we're passing in an environment, the scenario and the browser object we setup in the prior post. &amp;nbsp;The environment is really handy since QA folks might be running tests against different regions and would want to pass that information back to the developers to recreate problems. &amp;nbsp;The &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;TEST_SERVER&lt;/span&gt; environment variable is already used to pick this during test startup.&lt;br /&gt;
&lt;br /&gt;
The ErrorWriter module is next. &lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/917632.js?file=error_writer.rb"&gt;&lt;/script&gt;&lt;br /&gt;
Several things are going on here. &amp;nbsp;The write errors message is simple enough. &amp;nbsp;It creates a directory for the errors to be written, and dumps the context of the browser's text and html representations to file. &amp;nbsp;What's more interesting are the scenario_name and file_name methods. &lt;br /&gt;
&lt;br /&gt;
The scenario_name method does some interesting things. &amp;nbsp;We want to name our files by the name of the scenario that fails. &amp;nbsp;In the else clause, you see how dashes and white space are replaced by a single white space character. &amp;nbsp;But, in the if clause, we're testing to see if we have an &lt;a href="https://github.com/aslakhellesoy/cucumber/blob/master/lib/cucumber/ast/outline_table.rb"&gt;ExampleRow&lt;/a&gt;. &amp;nbsp;These are the Examples in a Cucumber Scenario Outline. &amp;nbsp;If the scenario is an ExampleRow, we lose the name of the Scenario itself. &amp;nbsp;Fortunately, the ExampleRow knows what &lt;a href="https://github.com/aslakhellesoy/cucumber/blob/master/lib/cucumber/ast/scenario_outline.rb"&gt;ScenarioOutline&lt;/a&gt; it belongs to. &amp;nbsp;We can get the name of the ScenarioOutline, perform the same transform on the name, and then append the example that is actually running to it's name.&lt;br /&gt;
&lt;br /&gt;
The file_name method simply concatenates the region, time of failure and the scenario name together for the filename to be written to.&lt;br /&gt;
&lt;br /&gt;
This can result in some very long file names, however, their descriptiveness wins overall. &amp;nbsp;You don't have to open a file to find out what failed. &amp;nbsp;A simple reading of the directory containing the files lets anyone find the test that they need to examine quickly.&lt;br /&gt;
&lt;br /&gt;
The write_errors method did one other thing. Call capture_screenshot to grab an image of the browser. &amp;nbsp;When using Watir, you had to do some fancy things with the Win32 API to make the capture work. &amp;nbsp;We had a lot of issues using it with our QA folks who had SnagIT installed. &amp;nbsp;So how refreshing it was to have watir-webdriver just work with so little code:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/917632.js?file=screenshots.rb"&gt;&lt;/script&gt;&lt;br /&gt;
We have another little trick, where in our prior example, we setup a &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;WEBDRIVER&amp;nbsp;&lt;/span&gt;variable to determine if we could take a screenshot. &amp;nbsp;If we're running on Celerity, no harm, no foul, but no screenshot. &lt;br /&gt;
&lt;br /&gt;
Granted, it's really selenium that we're using here, but we get really nice and small png files, which only contain the contents of the browser. &amp;nbsp;No title bar, no toolbars, nothing. Just the contents of the window. &amp;nbsp;Our only issue is that IE causes problems on our CI box, but just having Firefox able to run there is an outstanding step forward.&lt;br /&gt;
&lt;br /&gt;
Hopefully this is helpful, please let me know if it is.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-8395561670983667794?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/eQZ01vAwsREnNmgngRAy5LabiY4/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/eQZ01vAwsREnNmgngRAy5LabiY4/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/eQZ01vAwsREnNmgngRAy5LabiY4/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/eQZ01vAwsREnNmgngRAy5LabiY4/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TidGZtd5dWM:wSXhG0GlxK0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TidGZtd5dWM:wSXhG0GlxK0:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TidGZtd5dWM:wSXhG0GlxK0:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=TidGZtd5dWM:wSXhG0GlxK0:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TidGZtd5dWM:wSXhG0GlxK0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=TidGZtd5dWM:wSXhG0GlxK0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TidGZtd5dWM:wSXhG0GlxK0:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=TidGZtd5dWM:wSXhG0GlxK0:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/TidGZtd5dWM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/8395561670983667794/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=8395561670983667794" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/8395561670983667794?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/8395561670983667794?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/TidGZtd5dWM/making-your-cukes-more-awesomer-with.html" title="Making Your Cukes More Awesomer With Screenshots" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2011/04/making-your-cukes-more-awesomer-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEYAQH4zfCp7ImA9WhZREUw.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-348557235028246885</id><published>2011-04-06T14:55:00.000-04:00</published><updated>2011-04-06T14:55:41.084-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-06T14:55:41.084-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Cucumber" /><category scheme="http://www.blogger.com/atom/ns#" term="qa" /><category scheme="http://www.blogger.com/atom/ns#" term="watir-webdriver" /><category scheme="http://www.blogger.com/atom/ns#" term="celerity" /><title>Cucumber with Celerity and Watir-Webdriver</title><content type="html">Earlier this year, I had the problem of trying to enable our QA staff to run our current suite of &lt;a href="https://github.com/jarib/celerity"&gt;Celerity&lt;/a&gt; driven &lt;a href="https://github.com/aslakhellesoy/cucumber"&gt;Cucumber&lt;/a&gt; tests through the actual browsers that we were using. &amp;nbsp;One obvious solution is to use various flavors of &lt;a href="http://watir.com/"&gt;Watir&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
The main concern with this solution is that Waitr required native ruby instead of jruby. &amp;nbsp;Not a huge deal, but still an extra annoyance for folks in a java shop. &amp;nbsp;Requiring QA to switch between jruby and watir wasn't ideal. &amp;nbsp;Additionally, taking snapshots of the screen during a failed test was far from trivial.&lt;br /&gt;
&lt;br /&gt;
So instead, I embarked on a quest to use &lt;a href="https://github.com/jarib/watir-webdriver"&gt;watir-webdriver&lt;/a&gt; and Celerity together. &amp;nbsp;The advantage here is that watir-webdriver, through &lt;a href="http://code.google.com/p/selenium/wiki/RubyBindings"&gt;selenium-webdrive&lt;/a&gt;r, can talk to browsers and runs on jruby. &amp;nbsp;This&amp;nbsp;combination&amp;nbsp;was alluded to on several blog sites that I had found, but I never quite found something that put it all together for me. &amp;nbsp; Eventually, i got it working. Hopefully this will at least get you about 90% of the way there.&lt;br /&gt;
&lt;br /&gt;
The first step would be adding watir-webdriver to your &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Gemfile&lt;/span&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/885618.js?file=Gemfile"&gt;
&lt;/script&gt;&lt;br /&gt;
Following that we create a &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;browser.rb&lt;/span&gt; file that determines what sort of browser you are running. &amp;nbsp;I placed this in the support folder in the Cucumber project. &amp;nbsp;One thing to remember about this, is that you could also throw this method into your env.rb. &lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/885618.js?file=browser.rb"&gt;
&lt;/script&gt;&lt;br /&gt;
What's going on here?  We're using the environment variable &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;BROWSER&lt;/span&gt; to determine if we want to run something in a browser. &amp;nbsp;We then use another environment variable to determine which browser to use. &amp;nbsp;We even have an environment variable to determine what Firefox we want to use, which is a nice feature of selenium-webdriver. &amp;nbsp;At the end, &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Browser&lt;/span&gt; contains the class of browser we want to instantiate for tests, and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;BROWSER_TYPE&lt;/span&gt; contains the actual type of browser to&amp;nbsp;instantiate&amp;nbsp;for watir-webdriver.&lt;br /&gt;
&lt;br /&gt;
One other thing to note, when we use Celerity, we're setting the offset for index based searches to 0. &amp;nbsp;Unlike watir-webdriver, Watir uses 1th based arrays. &amp;nbsp;Celerity, being a wrapper of &lt;a href="http://htmlunit.sourceforge.net/"&gt;HtmlUnit&lt;/a&gt;, had to translate those arrays from 1 to 0 so that HtmlUnit would run properly. &amp;nbsp;By setting this value, we can use 0th based arrays, just like watir-webdriver does in Celerity.&lt;br /&gt;
&lt;br /&gt;
Next we setup our Cucumber Before block in &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;hooks.rb&lt;/span&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/885618.js?file=hooks.rb"&gt;
&lt;/script&gt;&lt;br /&gt;
In here, we simply instantiate the object. &amp;nbsp;In my case, we also needed to setup some URLs and turn off SSL for our Celerity testing.&lt;br /&gt;
&lt;br /&gt;
The &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Rakefile&lt;/span&gt; is probably a bit more complex due to our desire to run both headless and browser tests on our CI box, but it is helpful, since it uses the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;:browser&lt;/span&gt; task to setup the &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;BROWSER&lt;/span&gt; environment variable.&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/885618.js?file=Rakefile"&gt;
&lt;/script&gt;&lt;br /&gt;
Finally, to help our QA folks, three batch files to execute the tests.&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/885618.js?file=test-headless.bat"&gt;
&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/885618.js?file=test-ie.bat"&gt;
&lt;/script&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;script src="https://gist.github.com/885618.js?file=test-ff.bat"&gt;
&lt;/script&gt;&lt;br /&gt;
Hopefully you'll find this useful. Please let me know if you do.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-348557235028246885?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/jssd6wW78-AlcARFu5E2HHb8GvU/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/jssd6wW78-AlcARFu5E2HHb8GvU/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/jssd6wW78-AlcARFu5E2HHb8GvU/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/jssd6wW78-AlcARFu5E2HHb8GvU/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=EEMaoXYIKyU:AcW5CLZfNx0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=EEMaoXYIKyU:AcW5CLZfNx0:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=EEMaoXYIKyU:AcW5CLZfNx0:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=EEMaoXYIKyU:AcW5CLZfNx0:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=EEMaoXYIKyU:AcW5CLZfNx0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=EEMaoXYIKyU:AcW5CLZfNx0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=EEMaoXYIKyU:AcW5CLZfNx0:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=EEMaoXYIKyU:AcW5CLZfNx0:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/EEMaoXYIKyU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/348557235028246885/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=348557235028246885" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/348557235028246885?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/348557235028246885?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/EEMaoXYIKyU/cucumber-with-celerity-and-watir.html" title="Cucumber with Celerity and Watir-Webdriver" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2011/04/cucumber-with-celerity-and-watir.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkANSXw7fip7ImA9WhZSFE8.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-120638547737233979</id><published>2011-03-28T10:11:00.002-04:00</published><updated>2011-03-29T14:53:18.206-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-03-29T14:53:18.206-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java code-retreat prison" /><title>My First Code Retreat...in Prison  (Coding in the Clink III)</title><content type="html">This weekend, I had the privilege of being part of a &lt;a href="http://javaguys.wordpress.com/2011/03/27/coding-in-the-clink-iii-another-javaguys-code-retreat-at-mci/"&gt;Code Retreat in Prison&lt;/a&gt;. Being that I haven't been to a &lt;a href="http://www.coderetreat.com/"&gt;Code Retreat&lt;/a&gt;...period, it might seem odd that I would choose The Clink as the venue for my first Code Retreat. &lt;br /&gt;
&lt;br /&gt;
Along those lines, one of the main questions I got from folks on the outside is why? &amp;nbsp;For me, this was a fairly simpler question, but the answer is a bit more complex than I let on. &lt;br /&gt;
&lt;br /&gt;
The first reason is, I knew &lt;a href="http://twitter.com/dnwiebe"&gt;Dan&lt;/a&gt; from my time working at CheckFree. &amp;nbsp;I still remember, and tell stories, of Dan and I being in the same HR orientation together. Dan asked some very good questions surrounding code ownership, and inventions, and who owned inventions made at on our own time at home. &amp;nbsp;The poor HR staff hadn't a clue how to answer Dan's questions. &amp;nbsp;I suspect now they do, since they were excellent points. &lt;br /&gt;
&lt;br /&gt;
The second reason is I had the pleasure of working with &lt;a href="http://twitter.com/joelhelbling"&gt;Joel&lt;/a&gt;, while at Nationwide. &amp;nbsp;Joel became involved in working in the program on Tuesday nights at MCI. I found the stories of his experience&amp;nbsp;fascinating and inspiring. &amp;nbsp;The fact that Dan was involved, made it all the more interesting. &amp;nbsp;The third reason, however, is where it gets more complex.&lt;br /&gt;
&lt;br /&gt;
Growing up, I attended mass on a weekly basis. When you attend mass on a weekly basis you get to experience different style homilies from different priests. Each one has their own preffered style.  Our pastor tended to simplify things such that a 5 year old could understand it. Other priests essentially took the guidance provided by the church and recited it back such that it.felt like a lecture. The best homilies, like most good programming conference talks, were filled with stories that made the subject matter&amp;nbsp;relevant to your life today.&lt;br /&gt;
&lt;br /&gt;
My favorite homilies contained stories of when the father visited inmates in prison. There was nothing more&amp;nbsp;inspiring&amp;nbsp;than these stories of redemption and understanding. &amp;nbsp;In fact, it was very obvious, that some of these inmates had achieved a better understanding and faith than I had as a questioning teenager. &amp;nbsp;I found myself wishing I could do something like that for something I truly understood. &amp;nbsp;Fast forward twenty years, and I have that opportunity. &amp;nbsp;I couldn't teach an inmate about faith, but I could bring a skill that I had cultivated over time and maybe do my part. &amp;nbsp;How could I pass this up?&lt;br /&gt;
&lt;br /&gt;
So on Sunday morning I took the drive up to &lt;a href="http://www.drc.ohio.gov/Public/mci.htm"&gt;Marion Correctional Institution&lt;/a&gt;. &amp;nbsp;Myself, and seven other "outsiders" went into the prison and walked to the Life Line center. &amp;nbsp;One thing that became very apparent during the walk was that you were in prison. &amp;nbsp;You weren't in a place off to the side of the prison, a visitors area or anything like that. &amp;nbsp;You walked right past the kitchen and dining hall. &amp;nbsp;Which was a very good thing actually. &lt;a href="http://www.kairosprisonministry.org/"&gt;Kairos&lt;/a&gt; was also there this weekend. &amp;nbsp;There is nothing that will calm your nerves than hearing a hundred or so men,&amp;nbsp;throatily&amp;nbsp;singing gospel songs as part of that program in the dining hall.&lt;br /&gt;
&lt;br /&gt;
Once we made it to Life Line, we were greeted by the inmates who were very cordial and polite. We had short introductions and offered our skill levels in Java. &amp;nbsp;We then decided to have each outsider sit at a workstation and have the prisoners pair or triple with each of us. &amp;nbsp;I had the opportunity to triple with a beginner and a more intermediate to senior level inmate on our first effort on a Tennis Game Kata. &lt;br /&gt;
&lt;br /&gt;
We made a few obvious mistakes. &amp;nbsp;Trying to cram an enum into the problem wasn't ideal and sidetracked us. We probably should have ping ponged a bit more in our pairing style, as what ended up happening was our beginner programmer drove while myself and the more senior inmate acted in a coaching role. &amp;nbsp;Not that there's anything wrong&amp;nbsp;with&amp;nbsp;this approach, but we didn't make it very far in our effort to solve the problem. &amp;nbsp;This, of course, is OK, since you're going to toss the code and start over for round 2 with different pairs. &lt;br /&gt;
&lt;br /&gt;
We then had the opportunity to have lunch, which meant eating with the population in the dining hall. &amp;nbsp;With four people to a table, we intemingled and chatted over a brunch. &amp;nbsp;Brunch consisted of sausage, a hashbrown, an english muffin with jelly, some sort of bran flakes, scrambled eggs, a salad and a banana. &amp;nbsp;Seems like a lot of food until you realize, you don't get to snack on the inside. &amp;nbsp;One of the more interesting experiences was having a random inmate come and ask me if I wanted my banana. &amp;nbsp;Once I discovered that was fine, I gave it to him. &amp;nbsp;It's apparently quite common.&lt;br /&gt;
&lt;br /&gt;
During lunch, we had an interesting discussion about programming and its utility on the outside of being able to earn a living. &amp;nbsp;This was a popular refrain. &amp;nbsp;Could you get a job in programming? &amp;nbsp;I think some of these guys had seen internal programs go away due to foreign competition&amp;nbsp;and they wanted to know if there was a future in it. &amp;nbsp;Yes, the furniture building program had been halted since the state could get product cheaper overseas. &amp;nbsp;I think the choice of the prison to shift resources into technology is a very good move, it will help the inmates upon release regardless of programming's place.&lt;br /&gt;
&lt;br /&gt;
What was also interesting was learning that some of the guys had already programmed in prison before. I heard a story of guys working for a client in Arizona. &amp;nbsp;They ran into issues, however, when the client's programmer had a complex of working with programmers in prison. &amp;nbsp;Sounds similar to folks having concerns with working with offshore developers, doesn't it? Probably the most interesting thing about this situation was that all communication about the code was done over telephone. &amp;nbsp;No email, no instant message, no internet. &amp;nbsp;Just telephone and books. &amp;nbsp;That's tough! &lt;br /&gt;
&lt;br /&gt;
After lunch I had the opportunity to see a 90 second space battle video that an inmate rendered using &lt;a href="http://www.blender.org/"&gt;Blender&lt;/a&gt; and some other graphics tools. It was darn impressive. &amp;nbsp;There was serious skill involved in making the video. &lt;br /&gt;
&lt;br /&gt;
After the video we switched pairs, and I ended up with two fellows who I would classify as intermediate to advanced in programming skills. &amp;nbsp;We were able to solve the problem through&amp;nbsp;rotational&amp;nbsp;ping pong pairing. &amp;nbsp;Probably the most interesting thing about this was towards the end. &amp;nbsp;We believed we were done, until we read the final step of the kata which told us that the program should ignore all points after a game is won. &amp;nbsp;Writing tests for that feature drew up a bunch of tiny hidden bugs which gave me the opportunity to talk about self documenting code and extracting methods to handle those problems. &amp;nbsp;Which, one of my triple knew quite well...probably better than I did, but was a bit too modest.&lt;br /&gt;
&lt;br /&gt;
One of the things that&amp;nbsp;occurred&amp;nbsp;to me after the second pairing was how skilled some of these programmers were. &amp;nbsp;I can honestly say that some of these guys had more skill than folks who I've been involved in interviews with on the outside. &amp;nbsp;I&amp;nbsp;believe&amp;nbsp;a lot of it has to do with rote memorization. &amp;nbsp;They have no Internet. &amp;nbsp;Only books, Eclipse and each other. &amp;nbsp;Any plugins and tools have to be brought in from the outside. &amp;nbsp;They don't have &lt;a href="http://www.stackoverflow.com/"&gt;StackOverflow&lt;/a&gt; to ask questions. &amp;nbsp;Just each other, books, and whatever hints the IDE provides. So unlike most developers, who have adapted lazy instantiation of skills in order to adapt quickly to different technologies, these guys have had to battle harden the skills that they have. The other thing that surprised me, is how well they pair. &amp;nbsp;One thing you might not expect in a prison situation is the ability to share information, reduce your ego, and pass the keys back and forth. &amp;nbsp;Yet, they did it without concern. &amp;nbsp;Of the advanced programmers, I had the luxury of pairing with, I saw folks who would have no problem fitting in with an Agile team. &amp;nbsp;I know they could pass an audition, if they could get one on the outside.&lt;br /&gt;
&lt;br /&gt;
So when I reflect on my experience, it makes me take a step back, and think about my own ego when I pair. Am I giving up the keyboard enough? Am I ping ponging enough? &amp;nbsp;Am I letting a junior developer work through their problems enough, or do they need to shoot themselves in the foot a few times first? &amp;nbsp;It was great reflection for me in handling my professional life.&lt;br /&gt;
&lt;br /&gt;
One final note. &amp;nbsp;The guys in here have made mistakes. Some of these guys have made really big mistakes. &amp;nbsp;I didn't ask, but one of the inmates I paired with offered that he had been in the system for almost two decades.&amp;nbsp;&amp;nbsp;You can draw your own conclusions from that information.&amp;nbsp;He had serious questions on if learning Java could help him when he got to the outside. &amp;nbsp;I think it can, but as Dan put it at our retrospective dinner, the most important thing to learn is pairing and learning to work well with others.&amp;nbsp;I believe the folks in this program were trying to do whatever they can to build skills that can help them later, be it on the inside or outside.&lt;br /&gt;
&lt;br /&gt;
The most interesting thing about this experience, is how well it paralleled with the homilies given in my youth. &amp;nbsp;It became obvious to me the older I got, that the priest was trying to say that, these prisoners know more about faith that you understand due to their situation. &amp;nbsp;Similarly, these inmates knew more about programming than many folks I've encountered simply due to their faith that it's a skill that can help them later. &amp;nbsp;They practiced hard, and were better than they would ever care to admit. &amp;nbsp;Faith is something that isn't taught, but arrives, and it was inspiring to see. &lt;br /&gt;
&lt;br /&gt;
I strongly encourage any programmer somewhere near Marion (any by near I mean 2.5 hour drive...we had folks from Ann Arbor show up), to try to make an effort to make it to a Code Retreat there. &amp;nbsp;If you aren't...maybe a prison near you has a programming program you can volunteer in. &amp;nbsp;It will be worth your time.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-120638547737233979?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/MNvRNwOI-VVN2iXbpw401w8CK0I/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/MNvRNwOI-VVN2iXbpw401w8CK0I/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/MNvRNwOI-VVN2iXbpw401w8CK0I/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/MNvRNwOI-VVN2iXbpw401w8CK0I/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fQhSNjUUc80:VYtV3LmgQP4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fQhSNjUUc80:VYtV3LmgQP4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fQhSNjUUc80:VYtV3LmgQP4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=fQhSNjUUc80:VYtV3LmgQP4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fQhSNjUUc80:VYtV3LmgQP4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=fQhSNjUUc80:VYtV3LmgQP4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fQhSNjUUc80:VYtV3LmgQP4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=fQhSNjUUc80:VYtV3LmgQP4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/fQhSNjUUc80" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/120638547737233979/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=120638547737233979" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/120638547737233979?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/120638547737233979?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/fQhSNjUUc80/my-first-code-retreatin-prison-coding.html" title="My First Code Retreat...in Prison  (Coding in the Clink III)" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>3</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2011/03/my-first-code-retreatin-prison-coding.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0IFQXYycSp7ImA9Wx9QFUs.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-8441021729326111435</id><published>2010-12-28T13:35:00.008-05:00</published><updated>2010-12-28T15:51:50.899-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-28T15:51:50.899-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Cucumber" /><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Random Cucumber/Ruby Things I Learned</title><content type="html">Some random stuff I learned as a "seasoned" Java developer, working in Ruby and more specifically Cucumber for the first time this year, and not having a clue as to what I was doing.  I realize some of this stuff will be mind numbingly obvious to "seasoned" Ruby devs, but thought it would be interesting to share regardless.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Some background.  On my project some "legacy" Cucumber tests existed.  Legacy, you ask? How can Cucumber tests be legacy?  Simple, they were overly fragile, and violated DRY over and over again.  Therefore, since my overall group has chosen Cucumber as the ATDD tool of choice (and by extension Gherkin as the Story card language of choice), it was a good idea to get in and refactor the tests.  I had a good example in how to do this in the &lt;a href="http://www.cheezyworld.com/2010/11/09/ui-tests-not-brittle/"&gt;blog&lt;/a&gt; &lt;a href="http://www.cheezyworld.com/2010/11/13/ui-tests-part-two/"&gt;posts&lt;/a&gt; by &lt;a href="http://twitter.com/#!/chzy"&gt;Jeff Morgan&lt;/a&gt;, and I fortunately had someone who I could tap on the shoulder on a sister team in &lt;a href="http://twitter.com/#!/joelhelbling"&gt;Joel Helbling&lt;/a&gt;.  So with the goal of learning some Ruby in mind, and a copy of RubyMine I began tearing up the soil and planting cukes.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Firstly, when you're working with multiple versions of Ruby, in my case, &lt;a href="http://jruby.org/"&gt;JRuby&lt;/a&gt; and Ruby for Windows, you might need to modify your Gemfile by the platform.  In my case, I needed JRuby to run the tests the same way as the legacy tests were running.  JRuby drove &lt;a href="http://celerity.rubyforge.org/"&gt;Celerity&lt;/a&gt; (yay HtmlUnit!) tests on Hudson, while native Ruby drives Waitr/FireWatir browser driven versions of the same tests.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, although it's very obvious after the fact, here's a clue...there is no Celerity for Ruby.  HtmlUnit is a Java library, and Celerity is just a Waitr API based wrapper of it.  So it only works on JRuby.  I thought the Gemfile would just handle that, but I was very very wrong.  Therefore, you have to do something like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre name="code" class="brush: ruby"&gt;&lt;br /&gt;platforms :jruby do&lt;br /&gt; gem "celerity", "0.8.2"&lt;br /&gt; gem "syntax"&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;if RUBY_PLATFORM =~ /mswin|mingw/ #Jruby no likey mingw&lt;br /&gt; platforms :mswin, :mingw do&lt;br /&gt;   gem "watir", "1.6.7"&lt;br /&gt;   gem "watir-webdriver"&lt;br /&gt;   gem "win32console"&lt;br /&gt;   gem "win32-clipboard"&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This works out pretty well, just don't check in your Gemfile.lock. Ideally your QA testers will be running the Watir versions, while CI will maintain sanity when they can't.  If  you don't have QA testers, then I suggest having two versions of the project open, with separate Gemfile.lock that represent each platform. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'm certain there's a better way to do this...but I've not discovered it yet.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Another random thing.  If you want to pass a parameter (like a table) from one Cucumber step definition to another, you use a comma at the end of the line and pass the parameter in.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In my case, I was developing tests that triggered various UI based errors on each page during a process to say retrieve a password.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So I had a step definition that said:&lt;/div&gt;&lt;br /&gt;&lt;pre name="code" class="brush: ruby"&gt;&lt;br /&gt;When /^I forget my password and enter the credentials$/ do |table|&lt;br /&gt;  # table is a |username|account_number|&lt;br /&gt;  #blah blah blah with username and account_number&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div&gt;But what do I do when I have passed this step and need to enter security questions?  I want to go through the successful parts of this step to enter the next step.  How do I do this?&lt;/div&gt;&lt;br /&gt;&lt;pre name="code" class="brush: ruby"&gt;&lt;br /&gt;When /^I forget my password and enter the credentials and answer the security questions$/ do |table|&lt;br /&gt;  # table is a |username|account_number|answer1|answer2|answer3|&lt;br /&gt;  When 'I forget my password and enter the credentials', table&lt;br /&gt;  # Do stuff to answer the security questions&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div&gt;See that comma at the end of the step definition call?  It passes table to the next step and lets it grab username and account_number out of the hash.  I couldn't find this anywhere in documentation, which means it's painfully obvious to Ruby folks, but it's not if you don't know it. I'm sure it's documented somewhere, but I've failed at finding it.  But boy discovering that was quite useful.&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;One last thing that I can think of right now.  For some reason the version of JRuby I have to use doesn't like to use rspec.  So similarly, to the first problem, we need to do strange things to make JRuby happy in env.rb:&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="brush: ruby"&gt;&lt;br /&gt;require 'rubygems'&lt;br /&gt;if RUBY_PLATFORM =~ /java/ #JRuby is strange&lt;br /&gt;  require 'spec'&lt;br /&gt;else&lt;br /&gt;  require 'rspec'&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Hopefully these tips will be of some use to folks like myself who don't know enough Ruby to be dangerous...yet.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-8441021729326111435?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/c6Qpo3LhWjI8-8Zh-F6G6bI9PZM/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/c6Qpo3LhWjI8-8Zh-F6G6bI9PZM/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/c6Qpo3LhWjI8-8Zh-F6G6bI9PZM/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/c6Qpo3LhWjI8-8Zh-F6G6bI9PZM/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=GW0wWuiERVo:yPE4M9-ftQ8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=GW0wWuiERVo:yPE4M9-ftQ8:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=GW0wWuiERVo:yPE4M9-ftQ8:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=GW0wWuiERVo:yPE4M9-ftQ8:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=GW0wWuiERVo:yPE4M9-ftQ8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=GW0wWuiERVo:yPE4M9-ftQ8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=GW0wWuiERVo:yPE4M9-ftQ8:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=GW0wWuiERVo:yPE4M9-ftQ8:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/GW0wWuiERVo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/8441021729326111435/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=8441021729326111435" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/8441021729326111435?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/8441021729326111435?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/GW0wWuiERVo/random-cucumberruby-things-i-learned.html" title="Random Cucumber/Ruby Things I Learned" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2010/12/random-cucumberruby-things-i-learned.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkAHRXY4eip7ImA9Wx9QFUs.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-6729509620482367083</id><published>2010-08-18T15:42:00.004-04:00</published><updated>2010-12-28T15:38:54.832-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-28T15:38:54.832-05:00</app:edited><title>Cleaning up after maven-eclipse-plugin</title><content type="html">There are a few of us out there who are in situations where we cannot use the m2eclipse plugin to run our Maven based projects without pain.  To resolve that pain there's the maven-eclipse-plugin.&lt;br /&gt;&lt;br /&gt;Unfortunately, the current version of the plugin has a &lt;a href="http://jira.codehaus.org/browse/MECLIPSE-449"&gt;bug&lt;/a&gt; which causes an in issue where duplicate facet entries are placed in the org.eclipse.wst.common.project.facet.core.xml file.&lt;br /&gt;&lt;br /&gt;As an example:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="brush: xml"&gt;&lt;br /&gt;&amp;lt;faceted-project&amp;gt;&lt;br /&gt;&amp;lt;fixed facet="jst.java"/&amp;gt;&lt;br /&gt;&amp;lt;fixed facet="jst.utility"/&amp;gt;&lt;br /&gt;&amp;lt;installed facet="jst.utility" version="1.0"/&amp;gt;&lt;br /&gt;&amp;lt;installed facet="jst.java" version="5.0"/&amp;gt;&lt;br /&gt;&amp;lt;installed facet="jst.java" version="5.0"/&amp;gt;&lt;br /&gt;&amp;lt;installed facet="jst.utility" version="1.0"/&amp;gt;&lt;br /&gt;&amp;lt;/faceted-project&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;How to solve this problem?  A convient excuse to try out some Groovy and the &lt;a href="http://docs.codehaus.org/display/GMAVEN/Home"&gt;GMaven plugin&lt;/a&gt;.  Fortunately, Groovy is pretty good at modifying XML, and GMaven is pretty good at playing within the maven pom.&lt;br /&gt;&lt;br /&gt;Here's the Groovy script:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="brush: groovy"&gt;&lt;br /&gt;if (project.packaging != 'pom') {&lt;br /&gt;def facetXml = new File(project.basedir, '.settings/org.eclipse.wst.common.project.facet.core.xml')&lt;br /&gt;def facets = new XmlParser().parse(facetXml)&lt;br /&gt;def facetSet = [] as Set&lt;br /&gt;&lt;br /&gt;log.info('Loaded Eclipse Facet XML')&lt;br /&gt;facets.installed.each {&lt;br /&gt;  if (! facetSet.add(it.'@facet')) {&lt;br /&gt;    it.parent().remove(it)&lt;br /&gt;    log.info('Removed ' + it.'@facet')&lt;br /&gt;  } else {&lt;br /&gt;    log.info('Put in ' + it.'@facet')&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;log.info('backup old XML')&lt;br /&gt;new File(project.basedir, '.settings/org.eclipse.wst.common.project.facet.core.xml.bkp').delete()&lt;br /&gt;facetXml.renameTo(new File(project.basedir, '.settings/org.eclipse.wst.common.project.facet.core.xml.bkp'))&lt;br /&gt;log.info('writing items')&lt;br /&gt;def newFacetXml = new File(project.basedir, '.settings/org.eclipse.wst.common.project.facet.core.xml')&lt;br /&gt;&lt;br /&gt;new XmlNodePrinter().print(facets)&lt;br /&gt;&lt;br /&gt;PrintWriter pw = new PrintWriter(newFacetXml)&lt;br /&gt;xmlNodePrinter = new XmlNodePrinter(pw)&lt;br /&gt;xmlNodePrinter.setPreserveWhitespace(true)&lt;br /&gt;xmlNodePrinter.print(facets)&lt;br /&gt;pw.close()&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I'm sure there's some code golf you could play to shorten this up, but it does the trick.  When you run it, the two extra entries in the file are gone, and a harmless backup file is left behind.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-6729509620482367083?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/EewI0a8ximLSD5VF7ghllt5n9cA/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/EewI0a8ximLSD5VF7ghllt5n9cA/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/EewI0a8ximLSD5VF7ghllt5n9cA/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/EewI0a8ximLSD5VF7ghllt5n9cA/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=zYQkMDz6xsc:HrQ8w6oUpLo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=zYQkMDz6xsc:HrQ8w6oUpLo:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=zYQkMDz6xsc:HrQ8w6oUpLo:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=zYQkMDz6xsc:HrQ8w6oUpLo:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=zYQkMDz6xsc:HrQ8w6oUpLo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=zYQkMDz6xsc:HrQ8w6oUpLo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=zYQkMDz6xsc:HrQ8w6oUpLo:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=zYQkMDz6xsc:HrQ8w6oUpLo:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/zYQkMDz6xsc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/6729509620482367083/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=6729509620482367083" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/6729509620482367083?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/6729509620482367083?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/zYQkMDz6xsc/cleaning-up-after-maven-eclipse-plugin.html" title="Cleaning up after maven-eclipse-plugin" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2010/08/cleaning-up-after-maven-eclipse-plugin.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DU8FQns5eCp7ImA9WxFWFEQ.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-4100980401999244646</id><published>2010-06-02T12:09:00.006-04:00</published><updated>2010-06-02T12:56:53.520-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-02T12:56:53.520-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="waterfall" /><category scheme="http://www.blogger.com/atom/ns#" term="agile" /><category scheme="http://www.blogger.com/atom/ns#" term="lean" /><category scheme="http://www.blogger.com/atom/ns#" term="sabotage" /><title>Sabotaging Your IT Team, Straight From 1944.</title><content type="html">Today I ran across a highly entertaining post on &lt;a href="http://volokh.com/2010/06/01/sabotage-or-how-dilbert-won-the-war/"&gt;Volokh&lt;/a&gt; which links to a manual on sabotage used during World War II.  Most of the stuff in the manual is standard stuff, fires, blowing up machinery, etc.  However, the really entertaining stuff begins on page 28, which is the "corporate" section.  It details how to cause corporations to drive to a halt.&lt;br /&gt;&lt;br /&gt;There are many other blogs that could dissect and twist this into any number of ways.  Most of them have noticed how government tends to follow all of the recommendations as standard operation procedure.  But since this is a blog which has primarily focused on programming, I'll spin this towards Agile/Lean project methodologies and the old Waterfall standby. And some of this I'll just spin towards IT in general.&lt;br /&gt;&lt;br /&gt;So open up the &lt;a href="http://svn.cacert.org/CAcert/CAcert_Inc/Board/oss/OSS_Simple_Sabotage_Manual.pdf"&gt;PDF&lt;/a&gt; and skip ahead to page 28 of the Simple Sabotage Field Manual (page 32 in the PDF) and lets enjoy the mayhem.  Some of these are a bit harder to connect, but they're spinnable, and what's the fun without spin?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step a-1&lt;/span&gt;: Insist on using "channels".  No changes to your project to deliver what is actually needed when you notice it.  No team authority.  Push your request up, get the change orders done, then work like hell to make it happen.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step a-2&lt;/span&gt;: Make speeches.  You know the guy who rolls into a project tells you you're doing it all wrong, and your management listens and tells you to do it a different way?  That's this step.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step a-3&lt;/span&gt;: Refer matters to committee. Same as a-1.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step a-4&lt;/span&gt;: Bring up irrelevant issues.  Attempting to change work in the middle of a development iteration/sprint.  Unless you've got a major production outage, the issue can wait until the end of your work cycle.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step a-5&lt;/span&gt;: Haggle over precise wording. This is more of a problem when you cannot get good copy up front.  It can delay your work in waterfall and agile, so this one is just bad all around.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step a-6&lt;/span&gt;: Reopen closed issues. Reopening the design of the system without a major problem that you actually need to solve. Creating more work for works sake, not because there's debt to be paid off.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step a-7&lt;/span&gt;: Advocating "caution". Extra concern raised due to the unknown.  This is bad for a team since it reduced the confidence to achieve success.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step a-8&lt;/span&gt;: Be worried about authority to make a decision.  Being afraid to make a decision since you might not have authority.  You should have authority.  This is similar to a-1, a-3.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-1&lt;/span&gt;: Demand Written Orders.  Well, we all need specs, but in Agile/Lean, we're getting those as we need them.  We don't require the entire system be specified.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-2&lt;/span&gt;: Misunderstand Orders. Quibbling over specs.  This seems to occur when the business is not involved and rolls in several months down the line and says this isn't what they needed.  Eliminate using Show and Tells.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-3&lt;/span&gt;: Don't deliver until completely ready. Big Bang approach to delivery of software.  Yep, that's Waterfall.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-4&lt;/span&gt;: Don't order new working stocks until completely gone.  This is what is known as Kanban isn't it?  Pull work into the queues.  There is no pull, only the schedule.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-5&lt;/span&gt;: Order high quality materials which are hard to get.  I'll change this one.  Order expensive software/hardware that require expensive maintainence contracts.  Especially when there's a well maintained open source project that your company could fund 2 developers to work on full time at a much lower cost.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-6&lt;/span&gt;: Don't prioritize properly.  When the business does not work with the development team to prioritize features, then the wrong thing gets prioritized by one of the two parties involved.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-7&lt;/span&gt;: Insist on being perfect.  Misuse of metrics.  Insisting on 100% code coverage, zero issues in your quality checks as though it will eliminate Technical Debt.  It won't.  It will just make you move slower and ignores fixing the most expensive debt.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-8&lt;/span&gt;: Mistakes in routing.  Sorry I got nothing here.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-9&lt;/span&gt;: Give trainees misleading instructions.  This is more of a problem of new programmers not given enough time to gel into the new team.  Train them through pair programming.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-10&lt;/span&gt;: Be pleasant to ineffective workers, give them promotions.  Um, Dilbert cornered the market on this one.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-11&lt;/span&gt;: Hold conferences when there's important work to be done.  Eliminate meetings.  Prime Agile/Lean principle.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-12&lt;/span&gt;: Multiply paper work. Unnecessary documentation for sure, but also poor technical design which doesn't allow for code reuse.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-13&lt;/span&gt;: Multiply procedures.  Too many people required to approve change controls, code sign off.  Increased bureaucracy.  Agile, Lean, ITIL, even Six Sigma applied to software have reduced these issues. And more than likely, unless their a micromanaging jerk, the upper management doesn't want to be that involved.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Step b-14&lt;/span&gt;: Apply all regulations to the last letter.  Ever have a simple problem in a production install.  Say a test property file was installed instead of production and the entire project is backed out rather than let the team fix the problem on the fly?  Yeah, me too.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;There's more comedy gold in here, but since I like to blog by the seat of my pants without editing, I need to stop.  Heck, I've got a 1pm meeting to attend that is, well...let's just say that a-1, a-4, a-5 are to blame for this b-11.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-4100980401999244646?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/uX7TrGVwcKWb03ykRMyY7Nd6sWY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/uX7TrGVwcKWb03ykRMyY7Nd6sWY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/uX7TrGVwcKWb03ykRMyY7Nd6sWY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/uX7TrGVwcKWb03ykRMyY7Nd6sWY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=i9_PQ16iFws:-l5ZulONy8w:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=i9_PQ16iFws:-l5ZulONy8w:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=i9_PQ16iFws:-l5ZulONy8w:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=i9_PQ16iFws:-l5ZulONy8w:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=i9_PQ16iFws:-l5ZulONy8w:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=i9_PQ16iFws:-l5ZulONy8w:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=i9_PQ16iFws:-l5ZulONy8w:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=i9_PQ16iFws:-l5ZulONy8w:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/i9_PQ16iFws" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/4100980401999244646/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=4100980401999244646" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/4100980401999244646?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/4100980401999244646?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/i9_PQ16iFws/sabotaging-your-it-team-straight-from.html" title="Sabotaging Your IT Team, Straight From 1944." /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2010/06/sabotaging-your-it-team-straight-from.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkIARn4yeCp7ImA9WxBREEg.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-2202193237277486560</id><published>2009-12-28T22:47:00.001-05:00</published><updated>2009-12-28T22:49:07.090-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-28T22:49:07.090-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="trophymanager" /><category scheme="http://www.blogger.com/atom/ns#" term="TrExMa" /><title>TrExMa 0.8.6</title><content type="html">TrophyManager again changed their Squad page.  I've finally been able to find some time around the holidays to fix the TrExMa plugin.&lt;br /&gt;&lt;br /&gt;It's available at &lt;a href="http://code.google.com/p/trexma-for-firefox/"&gt;Google Code&lt;/a&gt; as version 0.8.6.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-2202193237277486560?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/BT6D7rayoqMRjBsa29SVc_AXvJg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BT6D7rayoqMRjBsa29SVc_AXvJg/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/BT6D7rayoqMRjBsa29SVc_AXvJg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BT6D7rayoqMRjBsa29SVc_AXvJg/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=AjcdmJRINkk:PyEU7_BDQ74:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=AjcdmJRINkk:PyEU7_BDQ74:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=AjcdmJRINkk:PyEU7_BDQ74:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=AjcdmJRINkk:PyEU7_BDQ74:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=AjcdmJRINkk:PyEU7_BDQ74:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=AjcdmJRINkk:PyEU7_BDQ74:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=AjcdmJRINkk:PyEU7_BDQ74:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=AjcdmJRINkk:PyEU7_BDQ74:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/AjcdmJRINkk" height="1" width="1"/&gt;</content><link rel="related" href="http://code.google.com/p/trexma-for-firefox/" title="TrExMa 0.8.6" /><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/2202193237277486560/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=2202193237277486560" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/2202193237277486560?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/2202193237277486560?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/AjcdmJRINkk/trexma-086.html" title="TrExMa 0.8.6" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/12/trexma-086.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkADQHYzcSp7ImA9Wx9QFUs.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-7738363552401046955</id><published>2009-11-17T08:56:00.009-05:00</published><updated>2010-12-28T15:39:31.889-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-28T15:39:31.889-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Woot-Off" /><category scheme="http://www.blogger.com/atom/ns#" term="winstone" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><title>Simple Web Tool Building With Maven (The Woot-Off Tracker Revisited)</title><content type="html">&lt;p&gt;One of my favorite tools of the past year was the &lt;a href="http://twofourone.blogspot.com/2008/11/air-based-woot-off-tracker.html"&gt;Woot-Off&lt;/a&gt; &lt;a href="http://twofourone.blogspot.com/2008/12/spry-woot-off-tracker.html"&gt;tracker&lt;/a&gt; I developed just over a year ago.  In my mind, one of the problems this tool had was that I needed to start up a proxy server to host make calls on behalf of the AJAX Javascript.  I needed to find some way to make this easier.  Additionally, I had run into an issue when I actually tried to run the tool behind a firewall proxy.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Since discovering the power of Maven, I thought I'd look into seeing what was available for the Winstone servlet engine I used on the first iteration of this tool.  Conviently, there was the &lt;a href="http://alchim.sourceforge.net/winstone-maven-plugin/usage.html"&gt;winstone-maven-plugin&lt;/a&gt;, a mojo which, among other things, built an executable jar for winstone with your war already deployed.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;This was perfect for my needs.  So (along with converting to apache's http-client) I set out on a new maven adventure.  My pom looked like this:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="brush: xml"&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&lt;br /&gt; xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&amp;gt;&lt;br /&gt; &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;br /&gt; &amp;lt;groupId&amp;gt;com.twofourone&amp;lt;/groupId&amp;gt;&lt;br /&gt; &amp;lt;artifactId&amp;gt;woot.tracker.proxy&amp;lt;/artifactId&amp;gt;&lt;br /&gt; &amp;lt;packaging&amp;gt;war&amp;lt;/packaging&amp;gt;&lt;br /&gt; &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;&lt;br /&gt; &amp;lt;name&amp;gt;woot.tracker.proxy&amp;lt;/name&amp;gt;&lt;br /&gt; &amp;lt;url&amp;gt;http://twofourone.blogspot.coom&amp;lt;/url&amp;gt;&lt;br /&gt; &amp;lt;dependencies&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;dependency&amp;gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;javax.servlet&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;servlet-api&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;version&amp;gt;2.4&amp;lt;/version&amp;gt;&lt;br /&gt;   &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br /&gt;  &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;dependency&amp;gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;javax.servlet.jsp&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;jsp-api&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;version&amp;gt;2.0&amp;lt;/version&amp;gt;&lt;br /&gt;   &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;&lt;br /&gt;  &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;dependency&amp;gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;version&amp;gt;4.6&amp;lt;/version&amp;gt;&lt;br /&gt;   &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;&lt;br /&gt;  &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;dependency&amp;gt;&lt;br /&gt;   &amp;lt;groupId&amp;gt;org.apache.httpcomponents&amp;lt;/groupId&amp;gt;&lt;br /&gt;   &amp;lt;artifactId&amp;gt;httpclient&amp;lt;/artifactId&amp;gt;&lt;br /&gt;   &amp;lt;version&amp;gt;4.0&amp;lt;/version&amp;gt;&lt;br /&gt;  &amp;lt;/dependency&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;/dependencies&amp;gt;&lt;br /&gt; &amp;lt;build&amp;gt;&lt;br /&gt;  &amp;lt;plugins&amp;gt;&lt;br /&gt;   &amp;lt;plugin&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;org.apache.maven.plugins&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;maven-compiler-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;version&amp;gt;2.0.2&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;configuration&amp;gt;&lt;br /&gt;     &amp;lt;source&amp;gt;1.5&amp;lt;/source&amp;gt;&lt;br /&gt;     &amp;lt;target&amp;gt;1.5&amp;lt;/target&amp;gt;&lt;br /&gt;    &amp;lt;/configuration&amp;gt;&lt;br /&gt;   &amp;lt;/plugin&amp;gt;&lt;br /&gt;   &amp;lt;plugin&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;net.sf.alchim&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;winstone-maven-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;executions&amp;gt;&lt;br /&gt;     &amp;lt;execution&amp;gt;&lt;br /&gt;      &amp;lt;goals&amp;gt;&lt;br /&gt;       &amp;lt;goal&amp;gt;embed&amp;lt;/goal&amp;gt;&lt;br /&gt;      &amp;lt;/goals&amp;gt;&lt;br /&gt;      &amp;lt;phase&amp;gt;package&amp;lt;/phase&amp;gt;&lt;br /&gt;     &amp;lt;/execution&amp;gt;&lt;br /&gt;    &amp;lt;/executions&amp;gt;&lt;br /&gt;   &amp;lt;/plugin&amp;gt;&lt;br /&gt;   &amp;lt;plugin&amp;gt;&lt;br /&gt;    &amp;lt;groupId&amp;gt;net.sf.alchim&amp;lt;/groupId&amp;gt;&lt;br /&gt;    &amp;lt;artifactId&amp;gt;winstone-maven-plugin&amp;lt;/artifactId&amp;gt;&lt;br /&gt;    &amp;lt;version&amp;gt;1.2&amp;lt;/version&amp;gt;&lt;br /&gt;    &amp;lt;configuration&amp;gt;&lt;br /&gt;     &amp;lt;cmdLineOptions&amp;gt;&lt;br /&gt;      &amp;lt;property&amp;gt;&lt;br /&gt;       &amp;lt;name&amp;gt;httpPort&amp;lt;/name&amp;gt;&lt;br /&gt;       &amp;lt;value&amp;gt;8480&amp;lt;/value&amp;gt;&lt;br /&gt;      &amp;lt;/property&amp;gt;&lt;br /&gt;      &amp;lt;property&amp;gt;&lt;br /&gt;       &amp;lt;name&amp;gt;ajp13Port&amp;lt;/name&amp;gt;&lt;br /&gt;       &amp;lt;value&amp;gt;-1&amp;lt;/value&amp;gt;&lt;br /&gt;      &amp;lt;/property&amp;gt;&lt;br /&gt;      &amp;lt;property&amp;gt;&lt;br /&gt;       &amp;lt;name&amp;gt;controlPort&amp;lt;/name&amp;gt;&lt;br /&gt;       &amp;lt;value&amp;gt;-1&amp;lt;/value&amp;gt;&lt;br /&gt;      &amp;lt;/property&amp;gt;&lt;br /&gt;      &amp;lt;property&amp;gt;&lt;br /&gt;       &amp;lt;name&amp;gt;directoryListings&amp;lt;/name&amp;gt;&lt;br /&gt;       &amp;lt;value&amp;gt;false&amp;lt;/value&amp;gt;&lt;br /&gt;      &amp;lt;/property&amp;gt;&lt;br /&gt;      &amp;lt;property&amp;gt;&lt;br /&gt;       &amp;lt;name&amp;gt;useInvoker&amp;lt;/name&amp;gt;&lt;br /&gt;       &amp;lt;value&amp;gt;false&amp;lt;/value&amp;gt;&lt;br /&gt;      &amp;lt;/property&amp;gt;&lt;br /&gt;     &amp;lt;/cmdLineOptions&amp;gt;&lt;br /&gt;    &amp;lt;/configuration&amp;gt;&lt;br /&gt;    &amp;lt;executions&amp;gt;&lt;br /&gt;     &amp;lt;execution&amp;gt;&lt;br /&gt;      &amp;lt;goals&amp;gt;&lt;br /&gt;       &amp;lt;goal&amp;gt;embed&amp;lt;/goal&amp;gt;&lt;br /&gt;      &amp;lt;/goals&amp;gt;&lt;br /&gt;      &amp;lt;phase&amp;gt;package&amp;lt;/phase&amp;gt;&lt;br /&gt;     &amp;lt;/execution&amp;gt;&lt;br /&gt;    &amp;lt;/executions&amp;gt;&lt;br /&gt;   &amp;lt;/plugin&amp;gt;&lt;br /&gt;  &amp;lt;/plugins&amp;gt;&lt;br /&gt;  &amp;lt;finalName&amp;gt;woot.tracker.proxy&amp;lt;/finalName&amp;gt;&lt;br /&gt; &amp;lt;/build&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;We have two setups for winstone.  The first goal, &lt;span style="font-style: italic;"&gt;embed&lt;/span&gt;, tells Maven to stick winstone into the jar during the package phase.  By default, this step will create a jar with the same name as your project and throw &lt;span style="font-style: italic;"&gt;-standalone&lt;/span&gt; on the end. The second setup tells winstone what port it should run on, etc.  These could potentially be in a single setup, just haven't tried it yet.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;In order to run the proxy, you simply execute the jar from a command prompt and you'll have a war deployed to port 8480. &lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;How has our proxy changed?  In order to support a firewall call, I decided to use Apache's &lt;a href="http://hc.apache.org/httpcomponents-client/index.html"&gt;HttpClient&lt;/a&gt;. Adding the dependency for HttpClient to the pom automagically downloads all dependencies of HttpClient for use by the war.  Our new code looks like this:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="brush: java"&gt;&lt;br /&gt;package com.twofourone.woot.tracker.proxy;&lt;br /&gt;&lt;br /&gt;import java.io.BufferedReader;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStreamReader;&lt;br /&gt;import java.io.OutputStream;&lt;br /&gt;import javax.servlet.ServletException;&lt;br /&gt;import javax.servlet.http.HttpServlet;&lt;br /&gt;import javax.servlet.http.HttpServletRequest;&lt;br /&gt;import javax.servlet.http.HttpServletResponse;&lt;br /&gt;&lt;br /&gt;import org.apache.http.HttpEntity;&lt;br /&gt;import org.apache.http.HttpHost;&lt;br /&gt;import org.apache.http.HttpResponse;&lt;br /&gt;import org.apache.http.client.HttpClient;&lt;br /&gt;import org.apache.http.client.methods.HttpGet;&lt;br /&gt;import org.apache.http.conn.params.ConnRoutePNames;&lt;br /&gt;import org.apache.http.impl.client.DefaultHttpClient;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Proxy Servlet to redirect requests to the Woot site.&lt;br /&gt;* @author mcornell&lt;br /&gt;*/&lt;br /&gt;public class WootProxy extends HttpServlet {&lt;br /&gt;&lt;br /&gt;   static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;   private HttpClient httpClient = new DefaultHttpClient();&lt;br /&gt;   private HttpGet wootGet;&lt;br /&gt;  &lt;br /&gt; /**&lt;br /&gt;  * Sets up the HttpClient with proxy info if provided. Sets up the URL for Woot.&lt;br /&gt;  */&lt;br /&gt; @Override&lt;br /&gt; public void init() throws ServletException {&lt;br /&gt;  String proxyHost = System.getProperty("proxyHost", "");&lt;br /&gt;    &lt;br /&gt;    String proxyPort = System.getProperty("proxyPort", "");&lt;br /&gt;    &lt;br /&gt;    if (proxyHost.length() &gt; 0 &amp;amp;&amp;amp; proxyPort.length() &gt; 0) {&lt;br /&gt;     HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort));&lt;br /&gt;     httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    String contentObj = "http://www.woot.com/salerss.aspx";&lt;br /&gt;       wootGet = new HttpGet(contentObj);&lt;br /&gt; }&lt;br /&gt;  &lt;br /&gt; &lt;br /&gt;   /**&lt;br /&gt;    * Processes requests for both HTTP &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; methods.&lt;br /&gt;    * @param request servlet request&lt;br /&gt;    * @param response servlet response&lt;br /&gt;    * @throws ServletException if a servlet-specific error occurs&lt;br /&gt;    * @throws IOException if an I/O error occurs&lt;br /&gt;    */&lt;br /&gt;   protected void processRequest(HttpServletRequest request, HttpServletResponse response)&lt;br /&gt;           throws ServletException, IOException {&lt;br /&gt;  &lt;br /&gt;  HttpResponse wootResponse = httpClient.execute(wootGet);&lt;br /&gt;      &lt;br /&gt;       HttpEntity wootEntity = wootResponse.getEntity();&lt;br /&gt;      &lt;br /&gt;       response.setContentType(wootEntity.getContentType().toString());&lt;br /&gt;       // Get and read the input stream.&lt;br /&gt;       StringBuffer buffer = new StringBuffer();&lt;br /&gt;&lt;br /&gt;       BufferedReader din =&lt;br /&gt;                      new BufferedReader(new InputStreamReader(wootEntity.getContent()));&lt;br /&gt;&lt;br /&gt;       String s;&lt;br /&gt;       while ((s = din.readLine()) != null) {&lt;br /&gt;           buffer.append(s);&lt;br /&gt;       }&lt;br /&gt;       din.close();&lt;br /&gt;&lt;br /&gt;       // Now write the bytes out to the client.&lt;br /&gt;       byte[] contentBytes = buffer.toString().getBytes();&lt;br /&gt;       OutputStream out = response.getOutputStream();&lt;br /&gt;       out.write(contentBytes, 0, contentBytes.length);&lt;br /&gt;       out.flush();&lt;br /&gt;       out.close();&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;    * Handles the HTTP &lt;code&gt;GET&lt;/code&gt; method.&lt;br /&gt;    * @param request servlet request&lt;br /&gt;    * @param response servlet response&lt;br /&gt;    * @throws ServletException if a servlet-specific error occurs&lt;br /&gt;    * @throws IOException if an I/O error occurs&lt;br /&gt;    */&lt;br /&gt;   @Override&lt;br /&gt;   protected void doGet(HttpServletRequest request, HttpServletResponse response)&lt;br /&gt;           throws ServletException, IOException {&lt;br /&gt;       processRequest(request, response);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;    * Handles the HTTP &lt;code&gt;POST&lt;/code&gt; method.&lt;br /&gt;    * @param request servlet request&lt;br /&gt;    * @param response servlet response&lt;br /&gt;    * @throws ServletException if a servlet-specific error occurs&lt;br /&gt;    * @throws IOException if an I/O error occurs&lt;br /&gt;    */&lt;br /&gt;   @Override&lt;br /&gt;   protected void doPost(HttpServletRequest request, HttpServletResponse response)&lt;br /&gt;           throws ServletException, IOException {&lt;br /&gt;       processRequest(request, response);&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   /**&lt;br /&gt;    * Returns a short description of the servlet.&lt;br /&gt;    * @return a String containing servlet description&lt;br /&gt;    */&lt;br /&gt;   @Override&lt;br /&gt;   public String getServletInfo() {&lt;br /&gt;       return "Woot Tracker Proxy";&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The code has changed to setup the client once and initialize it once.  Additionally, there is the code provided by the HttpClient project that adds a proxy to the client.  One additional change needs to be the ability to track a &lt;a href="http://kids.woot.com/"&gt;Kids.Woot-Off&lt;/a&gt; URL in case it's a different kind of woot-off.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The HTML/Javascript remains the same.  &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-7738363552401046955?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/n-mfvhfwXJMXGgpnY2Rvqpv1Xn8/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/n-mfvhfwXJMXGgpnY2Rvqpv1Xn8/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/n-mfvhfwXJMXGgpnY2Rvqpv1Xn8/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/n-mfvhfwXJMXGgpnY2Rvqpv1Xn8/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=uP9LcVeClEQ:8qqA7hAd9Kg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=uP9LcVeClEQ:8qqA7hAd9Kg:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=uP9LcVeClEQ:8qqA7hAd9Kg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=uP9LcVeClEQ:8qqA7hAd9Kg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=uP9LcVeClEQ:8qqA7hAd9Kg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=uP9LcVeClEQ:8qqA7hAd9Kg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=uP9LcVeClEQ:8qqA7hAd9Kg:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=uP9LcVeClEQ:8qqA7hAd9Kg:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/uP9LcVeClEQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/7738363552401046955/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=7738363552401046955" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/7738363552401046955?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/7738363552401046955?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/uP9LcVeClEQ/simple-web-tool-building-with-maven.html" title="Simple Web Tool Building With Maven (The Woot-Off Tracker Revisited)" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/11/simple-web-tool-building-with-maven.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UDSHo9fip7ImA9WxNbFEs.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-2206901165826323130</id><published>2009-10-30T14:25:00.007-04:00</published><updated>2009-11-17T09:47:59.466-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-17T09:47:59.466-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="sonar" /><category scheme="http://www.blogger.com/atom/ns#" term="hudson" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><title>Converting to the Ultimate Java Build Stack</title><content type="html">Over the past 6 months, I've had the pleasure and pain of working with Maven. Even with all of the pain, I've found myself a fan of the technology, assuming, of course, that you have the &lt;a href="http://juddsolutions.blogspot.com/2009/05/ultimate-enterprise-java-build-system.html"&gt;appropriate Maven stack&lt;/a&gt; set up.&lt;br /&gt;&lt;br /&gt;During my time at the client, I setup &lt;a href="http://hudson-ci.org/"&gt;Hudson&lt;/a&gt;, &lt;a href="http://nexus.sonatype.org/"&gt;Nexus&lt;/a&gt;, and &lt;a href="http://sonar.codehaus.org/"&gt;Sonar&lt;/a&gt; as outlined in Christopher Judd's blog post.  We had actually already decided upon Hudson and Nexus through discovery before Judd published his post.  Sonar was a nice discovery we added to the mix a bit later.  Having setup this stack, I thought I'd offer some thoughts on my experiences.&lt;br /&gt;&lt;br /&gt;I cannot stress how important having &lt;a href="http://repository.sonatype.org/index.html#welcome"&gt;Nexus&lt;/a&gt; (or Artifactory) is to keeping your Maven projects running smoothly and effectively. Having a repository management tool and proxy allows for all of your developers (and Hudsons) to simply point at the proxy and download everything required for a build quickly.  It also provides a place for you to host your third party jars from apps that have been purchased by your organization. Not everything is open source of course.&lt;br /&gt;&lt;br /&gt;The steps are simple enough, install Nexus and setup your settings.xml file to point to it.  For developers, they can use their own settings.xml file.  For Hudson, you may want to modify the settings.xml in your Maven install (especially if you're running on Windows).  You might need to add a mirror for Java.net's Maven 2 repository.  You might not.  It depends upon how up to date Maven's central repository is.&lt;br /&gt;&lt;br /&gt;Sonar was a nice surprise.  Providing PMD, Checkstyle and Cobertura results in a nice visual manner that allowed for quick feedback was surprisingly effective.  As a developer, I found it to be my favorite part of the switch to Maven.  Sure, Maven provides this information as part of its site goal, however, it doesn't present it like &lt;a href="http://nemo.sonar.codehaus.org/"&gt;this&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Obviously there are huge gains to be made by integrating Hudson and getting it to build upon version control commits, but I found more intriguing was its ability to trigger downstream builds.  When I built the commons library for the app, every maven artifact that used the commons library was triggered for build.  Numerous times, I discovered that I or someone else had broken something in a downstream library due to an API fix.  That instant feedback (through RSS feeds) was an invaluable time saver.&lt;br /&gt;&lt;br /&gt;What about Maven itself?  What does it gain you?  In the case I was working on, tremendous build granularity.  The original build used Ant, and was built in a monolithic manner.  Each component was built by the single script and bundled together.  One could argue it wasn't the best ant script in the world, and they'd be right.  Maven allowed us to break these dependencies apart.  So now, instead of having one monolithic code base which included what were really five separate artifacts, we now had five separate projects, each self contained using dependencies to draw in the needed artifacts.  As such the WAR and EAR builds ended up being very simple where they just drew in their needed dependencies and just worked.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Additionally, Maven offered unit testing at a very cheap cost.  The initial code base had no unit testing.  The developers were keen to use the debugger to test scenarios, which is needed to understand the mass amounts of reflection which were in use.  However, that's only helpful in understanding the code.  There should have been unit tests which were stood up to test each component on its own.  Maven automagically gives you unit testing with the surefire plugin enabled by default.  Make sure JUnit is a test dependency and start writing tests into the test source tree.  Keep your tests in the same package as your code and start gaining coverage. In between adding new features to the code base, I was able to increase code coverage on the base level components from 0 to 40%.  Obviously there's still a long way to go, but this was a vast improvement!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;One thing I will note about Maven is the tooling.  There is tremendous tooling available in the m2eclipse plugin for Eclipse.  Netbeans 6.5 and above opens Maven projects without needing to do much of anything.  It just opens the directory containing the pom and imports the project.  If you're moving to Maven, make sure that your development tools can support it.  As an example, RAD 7.0.x is Eclipse 3.2.  As such the m2eclipse plugin does not work for it.  If you &lt;i&gt;must&lt;/i&gt; use RAD 7.0.x, I would advise building your project outside of RAD.  Use a more up to date IDE to get the projects started and structures built.  Then use the maven-eclipse-plugin to build the proper project eclipse artifacts for your projects.  It will still be a challenge to build proper EAR and WAR projects for RAD.  Maven has a lot of help for this sort of pain.  However it can only be applied if you use the proper tools.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I've got a bit more to say about Maven, especially with my experiences of converting projects to Maven, the various pain and joy points I experienced and overall satisfaction.  But I'll leave that for another post.  Until then, I strongly suggest that if you have new development and you are looking for a pure Java based project build management tool, that you should take a long hard look at Maven.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-2206901165826323130?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/2FMM_22ivVBe-A7FYk1dzyvuNU0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/2FMM_22ivVBe-A7FYk1dzyvuNU0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/2FMM_22ivVBe-A7FYk1dzyvuNU0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/2FMM_22ivVBe-A7FYk1dzyvuNU0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fY2CxLSTZfs:NUXCCDuqlC4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fY2CxLSTZfs:NUXCCDuqlC4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fY2CxLSTZfs:NUXCCDuqlC4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=fY2CxLSTZfs:NUXCCDuqlC4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fY2CxLSTZfs:NUXCCDuqlC4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=fY2CxLSTZfs:NUXCCDuqlC4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=fY2CxLSTZfs:NUXCCDuqlC4:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=fY2CxLSTZfs:NUXCCDuqlC4:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/fY2CxLSTZfs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/2206901165826323130/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=2206901165826323130" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/2206901165826323130?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/2206901165826323130?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/fY2CxLSTZfs/converting-to-ultimate-java-build-stack.html" title="Converting to the Ultimate Java Build Stack" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/10/converting-to-ultimate-java-build-stack.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UBRn04fCp7ImA9WxNbFEs.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-751340390698967839</id><published>2009-09-14T16:40:00.008-04:00</published><updated>2009-11-17T09:47:37.334-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-11-17T09:47:37.334-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="site-deploy" /><category scheme="http://www.blogger.com/atom/ns#" term="hudson" /><category scheme="http://www.blogger.com/atom/ns#" term="maven" /><title>Wierdness with Maven's site-deploy and Hudson</title><content type="html">&lt;p&gt;So, for the past five months or so, I've been working on porting some Java projects to Maven and the &lt;a href="http://juddsolutions.blogspot.com/2009/05/ultimate-enterprise-java-build-system.html"&gt;Ultimate Enterprise Java Build System&lt;/a&gt;.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;A quick aside, Sonar is a very underrated tool.  You should try it.  I find the reporting very easy to use and very handy for working through potential coding mistakes in the legacy code I've been working with.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;One of those Java projects is a large multi-module Maven build.  It has five different modules, three Jar modules, a War module and *gasp* an Ear module.  It's been a bit of a beast to get going, but it seems to be working just fine.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;On Monday, I began to perform releases to move from Maven SNAPSHOTs to actual real version numbers.  Ah, the joys of SCM release management.  Thankfully, all of this pain ends up being worth it to easily tag and lock the code down using the &lt;a href="http://maven.apache.org/plugins/maven-release-plugin/"&gt;maven-release-plugin&lt;/a&gt;.  There were a couple of bumps, but it wasn't bad.&lt;/p&gt;&lt;p&gt;The fun began when I built my Hudson jobs to perform release builds.  These jobs simply pulled the tagged release and performed the following goals:&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;clean install site-deploy&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;Obviously, I used Hudson's deploy step for the artifacts rather than Maven's.  And everything was fine, until I got this:&lt;/p&gt;&lt;br /&gt;&lt;pre style="white-space: pre-wrap; word-wrap: break-word;"&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[ERROR] BUILD ERROR&lt;br /&gt;[INFO] ------------------------------------------------------------------------&lt;br /&gt;[INFO] Failed to resolve artifact.&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;Oops, what's that about?  A little more background.  This particular maven build was a multimodule build to create jar, war and ear files.  The artifact it couldn't resolve was the first jar it built! Scanning the log file, it became clear that the job was not installing the built jar to the repository.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;I don't know why that is, and am not certain if it's even worth opening a trouble ticket since the fix ended up being very simple.  It seems that site-deploy was causing issues with the flow of the job within Hudson.  By removing site-deploy the build ended up being successful.  The jar was compiled, tested and installed before the next module's build was invoked.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;I still wanted to build the site, so I investigated the &lt;a href="http://wiki.hudson-ci.org/display/HUDSON/M2+Extra+Steps+Plugin"&gt;M2 Extra Steps Plugin&lt;/a&gt;. This tool allows you to add multiple maven steps before and after the main job.  I added the site-deploy goal to run after the main install goal and the sites were generated!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-751340390698967839?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/4p9VbIgyefKa2HvHi62SEgq_ZlQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/4p9VbIgyefKa2HvHi62SEgq_ZlQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/4p9VbIgyefKa2HvHi62SEgq_ZlQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/4p9VbIgyefKa2HvHi62SEgq_ZlQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TEWTFGgF1-I:0E75pjqjTIE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TEWTFGgF1-I:0E75pjqjTIE:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TEWTFGgF1-I:0E75pjqjTIE:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=TEWTFGgF1-I:0E75pjqjTIE:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TEWTFGgF1-I:0E75pjqjTIE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=TEWTFGgF1-I:0E75pjqjTIE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=TEWTFGgF1-I:0E75pjqjTIE:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=TEWTFGgF1-I:0E75pjqjTIE:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/TEWTFGgF1-I" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/751340390698967839/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=751340390698967839" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/751340390698967839?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/751340390698967839?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/TEWTFGgF1-I/wierdness-with-mavens-site-deploy-and.html" title="Wierdness with Maven's site-deploy and Hudson" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/09/wierdness-with-mavens-site-deploy-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkENRX09eCp7ImA9WxJbF00.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-7156566614455461370</id><published>2009-07-27T09:16:00.002-04:00</published><updated>2009-07-27T09:18:14.360-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-27T09:18:14.360-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="trophymanager" /><category scheme="http://www.blogger.com/atom/ns#" term="TrExMa" /><title>Trexma for Firefox 3.5</title><content type="html">Been away from the blogging world for a while.  Been away from the Trexma world for a while.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;During that time, Firefox 3.5 was released and I hadn't had the opportunity to change a simple setting and redeploy the package for 3.5.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well that's been done now.  You can now download Trexma 0.8.5.1 from the &lt;a href="http://code.google.com/p/trexma-for-firefox/"&gt;google code site&lt;/a&gt;.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hopefully this helps. :)&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-7156566614455461370?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/keLjXOx38czzMXbpEuVDyiPUoSg/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/keLjXOx38czzMXbpEuVDyiPUoSg/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/keLjXOx38czzMXbpEuVDyiPUoSg/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/keLjXOx38czzMXbpEuVDyiPUoSg/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=QLiiq0mw7jM:SrK5or_ZbVI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=QLiiq0mw7jM:SrK5or_ZbVI:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=QLiiq0mw7jM:SrK5or_ZbVI:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=QLiiq0mw7jM:SrK5or_ZbVI:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=QLiiq0mw7jM:SrK5or_ZbVI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=QLiiq0mw7jM:SrK5or_ZbVI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/TwoFourOne?a=QLiiq0mw7jM:SrK5or_ZbVI:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/TwoFourOne?i=QLiiq0mw7jM:SrK5or_ZbVI:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/QLiiq0mw7jM" height="1" width="1"/&gt;</content><link rel="related" href="http://code.google.com/p/trexma-for-firefox/" title="Trexma for Firefox 3.5" /><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/7156566614455461370/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=7156566614455461370" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/7156566614455461370?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/7156566614455461370?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/QLiiq0mw7jM/trexma-for-firefox-35.html" title="Trexma for Firefox 3.5" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/07/trexma-for-firefox-35.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0YAR3syeip7ImA9WxVXE00.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-1165984268683622771</id><published>2009-02-10T15:57:00.008-05:00</published><updated>2009-02-10T16:25:46.592-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-10T16:25:46.592-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="US Mexico" /><category scheme="http://www.blogger.com/atom/ns#" term="US Soccer" /><title>US Mexico Tomorrow (the Mexi-whore edition)</title><content type="html">Going to go away from programming and into hijinks mode for a moment.  Too many memories coming back from eight years ago during the epic 2001 battle between the US and Mexico in Columbus.&lt;br /&gt;&lt;br /&gt;As with most big sporting events that actually allow in the inebriated public, the stands can be an interesting place.  I stood in about 2 rows from the top of the North End, directly behind the goal.  It was from this angle that I was able to see some mighty entertaining things.&lt;br /&gt;&lt;br /&gt;The first was the Mexi-whore.  Someone, obviously during a highly energetic brainstorming session fueled by Jager or Firewater, decided to purchase an adult doll.  You know the kind of doll I'm talking about.  The owner then acquired an undergarment for the doll, a thong of some nature, as well as a Mexican jersey.  At various times during the match, the doll was held aloft by a leg while a cohort proceeded to spank the doll.&lt;br /&gt;&lt;br /&gt;Needless to say, the presence of this doll has reached legendary status.  But today I have found proof! In this snap from YouTube, you can see the head of the doll circled.  It did exist.  I can only hope it will make a return.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_TxqtW3PNwtM/SZHtvvuaNNI/AAAAAAAABio/h9ewTDDGGk4/s1600-h/mexiwhore.JPG"&gt;&lt;img style="cursor: pointer; width: 400px; height: 266px;" src="http://4.bp.blogspot.com/_TxqtW3PNwtM/SZHtvvuaNNI/AAAAAAAABio/h9ewTDDGGk4/s400/mexiwhore.JPG" alt="" id="BLOGGER_PHOTO_ID_5301279640912540882" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Other entertaining items were the signs.  This sign would not made it through today's PC filters.  The text of it read: Red Card, Yellow Card, Mexican&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_TxqtW3PNwtM/SZHwK_KVbxI/AAAAAAAABiw/9g4XWr5i0zg/s1600-h/mexicancard.JPG"&gt;&lt;img style="cursor: pointer; width: 400px; height: 261px;" src="http://4.bp.blogspot.com/_TxqtW3PNwtM/SZHwK_KVbxI/AAAAAAAABiw/9g4XWr5i0zg/s400/mexicancard.JPG" alt="" id="BLOGGER_PHOTO_ID_5301282307935923986" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Yep, definitely wouldn't be "allowed" today.  It also shows you how different things were eight years ago.  Most everyone had a good time...some had too much of one.  There was that one drunken guy who thought it would be a good idea to take taunting to the next level and to moon a bunch of Mexican fans in the parking lot.  It might have been harmless had their not been a slew of vulgarity and elementary school kids in the car.  Needless to say, he almost got his ass kicked.&lt;br /&gt;&lt;br /&gt;The full YouTube video for your enjoyment:&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/XdUGy--8-Pc&amp;amp;hl=en&amp;amp;fs=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/XdUGy--8-Pc&amp;amp;hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;At any rate, the hijinks tomorrow should be outstanding.  If you're attending, I hope to see you there.  If you're watching at home, hopefully you'll see some antics on TV or YouTube in the days to come.  I'm hoping for a big US win, in the slop and wind that will be Columbus Crew Stadium.  And maybe the return of the Mexi-whore.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-1165984268683622771?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/EsNnwCiZ63F7OocDUZ_hWE0TH4Y/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/EsNnwCiZ63F7OocDUZ_hWE0TH4Y/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/EsNnwCiZ63F7OocDUZ_hWE0TH4Y/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/EsNnwCiZ63F7OocDUZ_hWE0TH4Y/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=cInO2RJ5"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=bKYP4wvR"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=y79VIy4k"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=y79VIy4k" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=pltTA7Ur"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=pltTA7Ur" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=niSfh3je"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=niSfh3je" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/B0vqjoGT7p4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/1165984268683622771/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=1165984268683622771" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/1165984268683622771?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/1165984268683622771?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/B0vqjoGT7p4/us-mexico-tomorrow-mexi-whore-edition.html" title="US Mexico Tomorrow (the Mexi-whore edition)" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_TxqtW3PNwtM/SZHtvvuaNNI/AAAAAAAABio/h9ewTDDGGk4/s72-c/mexiwhore.JPG" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/02/us-mexico-tomorrow-mexi-whore-edition.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkEMSHY9eSp7ImA9WxVQE0g.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-7776313950814109255</id><published>2009-01-30T16:22:00.001-05:00</published><updated>2009-01-30T16:24:49.861-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-30T16:24:49.861-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="trophymanager" /><category scheme="http://www.blogger.com/atom/ns#" term="TrExMa" /><title>TrExMa 0.8.5 is out</title><content type="html">TrExMa 0.8.5 is &lt;a href="http://code.google.com/p/trexma-for-firefox/"&gt;released&lt;/a&gt;.  This includes a bunch of code cleanup, removal of the old quick lineup in favor of the complete Hungarian Algorithm method as well as converting the Shortlist, and Youth Development to use the new codebase.&lt;br /&gt;&lt;br /&gt;If all goes well, this could be the final "preview" before deprecating 0.6.2.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-7776313950814109255?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/hg7RhtATLnPg1WW7vPjep6UoTrU/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/hg7RhtATLnPg1WW7vPjep6UoTrU/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/hg7RhtATLnPg1WW7vPjep6UoTrU/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/hg7RhtATLnPg1WW7vPjep6UoTrU/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=ljFgIgBp"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=1uApAT3q"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=0NKaWBKk"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=0NKaWBKk" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=csrv1sSB"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=csrv1sSB" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=lLy3O686"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=lLy3O686" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/daki5PhcboA" height="1" width="1"/&gt;</content><link rel="related" href="http://code.google.com/p/trexma-for-firefox/" title="TrExMa 0.8.5 is out" /><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/7776313950814109255/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=7776313950814109255" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/7776313950814109255?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/7776313950814109255?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/daki5PhcboA/trexma-085-is-out.html" title="TrExMa 0.8.5 is out" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/01/trexma-085-is-out.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8EQHgyeip7ImA9Wx9QFUs.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-5722192884309185557</id><published>2009-01-22T16:41:00.007-05:00</published><updated>2010-12-28T15:40:01.692-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-28T15:40:01.692-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="Hungarian Algorithm" /><category scheme="http://www.blogger.com/atom/ns#" term="TrExMa" /><title>The Hungarian Algorithm in Javascript</title><content type="html">For the past 7 months or so, I've been dabbling in XUL/Javascript as evidenced by the posts you can find on here about TrophyManager.com and the TrExMa plugin I created for the game.&lt;br /&gt;&lt;br /&gt;Recently, I got to tackle an old school problem.  Only I didn't know it was old school at the time when I started.  I suppose that's because I've not been doing hardcore Computer Science type problems in quite some time.&lt;br /&gt;&lt;br /&gt;Here's the problem:  In TrophyManager, you field a soccer team of 11 players, 10 field players and a keeper.  TrExMa calculates a skill for each player on your roster to try to give the user an idea of what the best possible lineup would be.  Selecting the keeper is easy.  Take the guy with the highest value.  Selecting the field players, not so much.&lt;br /&gt;&lt;br /&gt;Each player has 14 "visible" attributes.  Early players of the game used the instruction manual to determine which attribute is most important for the position the player is in.  For example, a Forward must be good at Finishing and Heading, etc.  Additionally, each player has a favorite position.  When they play "away" from their position, their skill drops.  So a player may have a high finishing skill, but since they like to play left back, they're not going to be a very good striker and would have their skill reduced anywhere from 4-40% depending upon a hidden adaptability attribute.&lt;br /&gt;&lt;br /&gt;Anyway, how does one figure out the best team?  I can set the adaptablity factor from 1-10% and we can then caclulate the skill for every position on the field for every player on the team.  Sounds good, right?  But how to pick it?&lt;br /&gt;&lt;br /&gt;I first looked at the original TrExMa algorithm within Excel.  The original app had a drop down to determine the depth of the search.  Once I looked at the code it was confirmed.  TrExMa used a series of nested loops to build the lineup.  If I'm not mistaken it could run from O(n^5) to O(n^10) depending upon the complexity you chose.  Wow, no wonder it got very slow.  I needed something else.  I also realized that the only way this algorithm would guaruntee success would be to run it at O(n^10) setting.  Using Javascript, this was simply unacceptable.  I couldn't get away with something running for so long.&lt;br /&gt;&lt;br /&gt;My first instinct was to use an algorithm similar to &lt;a href="http://en.wikipedia.org/wiki/Bin_packing_problem"&gt;Fit First&lt;/a&gt;.  In this case, find the highest value on the board, and place the player there.  cross him out and cross out the position.  Do it again until you've filled the positions.  Supposedly this runs in O(n log n). Of course, 10 is pretty small for me, as I've only 10 boxes to fill.  It's pretty good, but doesn't guaruntee the best match of players to positions.  Why?  Let's say you've got a player who is adept at a midfield and wing position.  And another player who is only good at midfield.  If the former is slightly better at midfield, he'll be placed there, and the other player will have to play somewhere else where he won't be as successful.&lt;br /&gt;&lt;br /&gt;How do you correct this?  I soon realized that this had to be a common problem, I just couldn't figure out what to call it?  Eventually I discovered that it's known as an "&lt;a href="http://en.wikipedia.org/wiki/Assignment_problem"&gt;assignment problem&lt;/a&gt;". It's an obvious name, now that I know what it is.  The classical use of this problem is to determine the least overall cost of doing multiple things with multiple resources.  It's very similar.&lt;br /&gt;&lt;br /&gt;Soon I discovered the &lt;a href="http://en.wikipedia.org/wiki/Hungarian_algorithm"&gt;Hungarian Algorithm&lt;/a&gt;.  It runs in O(n^4) (potentially O(n^3)).  So it's not fast, but it's not insanely slow.  It's not exponential, and it runs faster than the Excel app.&lt;br /&gt;&lt;br /&gt;So what does the Hungarian Algorithm do?  It uses a method of &lt;span style="font-style: italic;"&gt;starring &lt;/span&gt;and &lt;span style="font-style: italic;"&gt;priming &lt;/span&gt;to determine when a player is in its ideal place.  Essentially, if two players are "best" at a position, it will not accept that assignment until it validates the best organization of those players.  It's explained very well in the Wikipedia page.&lt;br /&gt;&lt;br /&gt;There are many implementations of the Hungarian Algorithm on the net but none in Javascript.  So here's one.  I've pulled out some of the specifics for my implementation.  You'll want to implement your own loadMatrix and getSolution methods.  One thing to note, is that with my problem, we had to reverse the matrix.  This is required because the Hungarian Algorithm uses small values to determine the best player (worker) for a position (job).  I hope you find this useful.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="brush: js"&gt;&lt;br /&gt;/* Implementation of the Hungarian Algorithm to determine&lt;br /&gt;* "best" squad.  This is a "reverse" implementation.&lt;br /&gt;* References:&lt;br /&gt;* http://en.wikipedia.org/wiki/Hungarian_algorithm&lt;br /&gt;* http://www.ams.jhu.edu/~castello/362/Handouts/hungarian.pdf (Example #2)&lt;br /&gt;* http://www.public.iastate.edu/~ddoty/HungarianAlgorithm.html // Non-square&lt;br /&gt;*/&lt;br /&gt;var Hungarian = window.Hungarian = window.HG = {&lt;br /&gt;&lt;br /&gt; /* 2 dimension arrays */&lt;br /&gt; skillMatrix: null,&lt;br /&gt; matrix: null,&lt;br /&gt; stars: null,&lt;br /&gt; /* Single arrays */&lt;br /&gt; rCov: [],&lt;br /&gt; cCov: [],&lt;br /&gt; rows: 0,&lt;br /&gt; cols: 0,&lt;br /&gt; dim: 0,&lt;br /&gt; solutions: 0, // "k"&lt;br /&gt; FORBIDDEN_VALUE: -999999,&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; /* Rows MUST BE the Formation (Jobs)&lt;br /&gt;  * Columns MUST BE the Squad (Workers)&lt;br /&gt;  * Therefore, the Rows MUST BE PADDED&lt;br /&gt;  */&lt;br /&gt; hungarianAlgortithm: function(formation, squad) {&lt;br /&gt;   HG.init(formation, squad);&lt;br /&gt;   // Step 1&lt;br /&gt;   HG.matrix = HG.subtractRowMins(HG.matrix);&lt;br /&gt;   // Step 2&lt;br /&gt;   HG.findZeros(HG.matrix);&lt;br /&gt;   var done = false;&lt;br /&gt;   while (!done) {&lt;br /&gt;     // Step 3&lt;br /&gt;     var covCols = HG.coverColumns(HG.matrix);&lt;br /&gt;     if (covCols &amp;gt; HG.solutions -1) {&lt;br /&gt;       done = true;&lt;br /&gt;     }&lt;br /&gt;     if (!done) {&lt;br /&gt;    // Step 4 (calls Step 5)&lt;br /&gt;       done = HG.coverZeros(HG.matrix);&lt;br /&gt;       while (done) {&lt;br /&gt;         // Step 6&lt;br /&gt;         var smallest = HG.findSmallestUncoveredVal(HG.matrix);&lt;br /&gt;         HG.matrix = HG.uncoverSmallest(smallest, HG.matrix);&lt;br /&gt;         done = HG.coverZeros(HG.matrix);&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return HG.getSolution(formation, squad)&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; init: function(formation, squad) {&lt;br /&gt;   HG.cols = squad.players.length;&lt;br /&gt;   HG.rows = formation.length;&lt;br /&gt;   HG.dim = Math.max(HG.rows, HG.cols);&lt;br /&gt;   HG.solutions = HG.dim;&lt;br /&gt;   HG.skillMatrix = HG.initMatrix(HG.rows, HG.cols);&lt;br /&gt;   HG.matrix = HG.initMatrix(HG.dim, HG.dim);&lt;br /&gt;   HG.stars = HG.initMatrix(HG.dim, HG.dim);&lt;br /&gt;   HG.matrix = HG.loadMatrix(squad, formation, HG.matrix, true);&lt;br /&gt;   HG.skillMatrix = HG.loadMatrix(squad, formation, HG.skillMatrix, false);&lt;br /&gt;&lt;br /&gt;   HG.rCov = new Array(HG.dim);&lt;br /&gt;   HG.cCov = new Array(HG.dim);&lt;br /&gt;   HG.initArray(HG.cCov, 0); // Zero it out&lt;br /&gt;   HG.initArray(HG.rCov, 0);&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; initMatrix: function(sizeX, sizeY) {&lt;br /&gt;   var matrix = new Array(sizeX);&lt;br /&gt;   for (var i=0; i&amp;lt;sizeX; i++) {&lt;br /&gt;     matrix[i] = new Array(sizeY);&lt;br /&gt;     HG.initArray(matrix[i], 0);&lt;br /&gt;   }&lt;br /&gt;   return matrix;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; // Takes an array of positions as a formation.&lt;br /&gt; // Takes a squad which contains an array of players&lt;br /&gt; loadMatrix: function(squad, formation, matrix, reverse) {&lt;br /&gt;   matrix = loadYourMatrix(squad, formation, matrix); // I've removed my implementation here. Far too much stuff&lt;br /&gt;   if (reverse) {&lt;br /&gt;   // This reverses the matrix.  We need to to create a cost based solution.&lt;br /&gt;     matrix = HG.reverseMatrix(HG.findMaxValue(matrix), matrix);&lt;br /&gt;   }&lt;br /&gt;   return matrix;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; findMaxValue: function(matrix) {&lt;br /&gt;   var max = 0.0;&lt;br /&gt;   for (var i = 0; i &amp;lt; matrix.length; i ++) {&lt;br /&gt;     for (var j = 0; j &amp;lt; matrix[i].length; j++) {&lt;br /&gt;       if (matrix[i][j] &amp;gt; max) {&lt;br /&gt;         max = matrix[i][j];&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return Number(max);&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; reverseMatrix: function(max, matrix) {&lt;br /&gt;   for (var i = 0; i &amp;lt; matrix.length; i ++) {&lt;br /&gt;     for (var j = 0; j &amp;lt; matrix[i].length; j++) {&lt;br /&gt;       matrix[i][j] = (Number(max) - Number(matrix[i][j])).toFixed(0);&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return matrix;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; subtractRowMins: function(matrix) {&lt;br /&gt;   for (var i = 0; i &amp;lt; matrix.length; i ++) {&lt;br /&gt;     var min = Number.MAX_VALUE;&lt;br /&gt;     for (var j = 0; j &amp;lt; matrix[i].length; j++) {&lt;br /&gt;       if (matrix[i][j] &amp;lt; min) {&lt;br /&gt;         min = Number(matrix[i][j]);&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;     for (var k = 0; k &amp;lt; matrix[i].length; k++) {&lt;br /&gt;       matrix[i][k] = matrix[i][k] - min;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;  return matrix;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; subtractColMins: function(matrix) {&lt;br /&gt;   for (var j = 0; j &amp;lt; matrix[0].length; j ++) {&lt;br /&gt;     var min = Number.MAX_VALUE;&lt;br /&gt;     for (var i = 0; i &amp;lt; matrix.length; i++) {&lt;br /&gt;       if (matrix[i][j] &amp;lt; min) {&lt;br /&gt;         min = Number(matrix[i][j]);&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;     for (var k = 0; k &amp;lt; matrix[0].length; k++) {&lt;br /&gt;       matrix[k][j] = matrix[k][j] - min;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return matrix;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; findZeros: function(matrix) {&lt;br /&gt;   for (var i = 0; i &amp;lt; matrix.length; i++) {&lt;br /&gt;     for (var j = 0; j &amp;lt; matrix[i].length; j++) {&lt;br /&gt;       if (matrix[i][j] == 0) {&lt;br /&gt;         if (HG.rCov[i] == 0 &amp;amp;&amp;amp; HG.cCov[j] == 0) {&lt;br /&gt;           HG.stars[i][j] = 1;&lt;br /&gt;           HG.cCov[j] = 1;&lt;br /&gt;           HG.rCov[i] = 1;&lt;br /&gt;         }&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   // Clear Covers&lt;br /&gt;   HG.initArray(HG.cCov,0);&lt;br /&gt;   HG.initArray(HG.rCov,0);&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; initArray: function(theArray, initVal) {&lt;br /&gt;   for (var i = 0; i &amp;lt; theArray.length; i++) {&lt;br /&gt;     theArray[i] = Number(initVal);&lt;br /&gt;   }&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; coverColumns: function(matrix) {&lt;br /&gt;   var count = 0;&lt;br /&gt;   for (var i=0; i &amp;lt; matrix.length; i++) {&lt;br /&gt;     for (var j=0; j &amp;lt; matrix[i].length; j++) {&lt;br /&gt;       if (HG.stars[i][j] == 1) {&lt;br /&gt;         HG.cCov[j] = 1;&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   for (var k=0; k &amp;lt; HG.cCov.length; k++) {&lt;br /&gt;     count = Number(HG.cCov[k]) + Number(count);&lt;br /&gt;   }&lt;br /&gt;   return count;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; /**&lt;br /&gt;  * step 4&lt;br /&gt;  * Cover all the uncovered zeros one by one until no more&lt;br /&gt;  * cover the row and uncover the column&lt;br /&gt;  */&lt;br /&gt; coverZeros: function(matrix) {&lt;br /&gt;   var retVal = true;&lt;br /&gt;  var zero = HG.findUncoveredZero(matrix); // Returns a Coords object..&lt;br /&gt;  &lt;br /&gt;   while (zero.row &amp;gt; -1 &amp;amp;&amp;amp; retVal == true) {&lt;br /&gt;     HG.stars[zero.row][zero.col] = 2 //Prime it&lt;br /&gt;     var starCol = HG.foundStarInRow(zero.row, matrix);&lt;br /&gt;     if (starCol &amp;gt; -1) {&lt;br /&gt;       HG.rCov[zero.row] = 1;&lt;br /&gt;       HG.cCov[starCol] = 0;&lt;br /&gt;     } else {&lt;br /&gt;       HG.starZeroInRow(zero); // Step 5&lt;br /&gt;       retVal = false;&lt;br /&gt;     }&lt;br /&gt;     if (retVal == true) {&lt;br /&gt;       zero = HG.findUncoveredZero(matrix);&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return retVal;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; findUncoveredZero: function(matrix) {&lt;br /&gt;   var coords = new HgCoords();&lt;br /&gt;   for (var i=0; i&amp;lt; matrix.length; i++) {&lt;br /&gt;     for (var j=0; j &amp;lt; matrix[i].length; j++) {&lt;br /&gt;       if (matrix[i][j] == 0 &amp;amp;&amp;amp; HG.rCov[i] == 0 &amp;amp;&amp;amp; HG.cCov[j] == 0) {&lt;br /&gt;         coords.row = i;&lt;br /&gt;         coords.col = j;&lt;br /&gt;         j = matrix[i].length;&lt;br /&gt;         i = matrix.length - 1;&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;   }&lt;br /&gt;   return coords;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; foundStarInRow: function(zeroRow, matrix) {&lt;br /&gt;   var retVal = -1;&lt;br /&gt;   for (var j = 0; j &amp;lt; matrix[zeroRow].length; j++) {&lt;br /&gt;     if (HG.stars[zeroRow][j] == 1) {&lt;br /&gt;       retVal = j;&lt;br /&gt;       j = matrix[zeroRow].length;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return retVal;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; /**&lt;br /&gt;  * step 5&lt;br /&gt;  * augmenting path algorithm&lt;br /&gt;  * go back to step 3&lt;br /&gt;  */&lt;br /&gt; starZeroInRow: function(zero) { // Takes a Coords Object&lt;br /&gt;   TrU.log("Step 5: Uncovered Zero:" + zero.row + "," + zero.col, TrU.DEBUG );&lt;br /&gt;   var done = false;&lt;br /&gt;   var count = 0;&lt;br /&gt;   var path = HG.initMatrix(HG.dim*2, 2);&lt;br /&gt;&lt;br /&gt;   path[count][0] = zero.row;&lt;br /&gt;   path[count][1] = zero.col;&lt;br /&gt;   while (!done) {&lt;br /&gt;     var row = HG.findStarInCol(path[count][1]);&lt;br /&gt;     if (row &amp;gt; -1) {&lt;br /&gt;       count++;&lt;br /&gt;       path[count][0] = row;&lt;br /&gt;       path[count][1] = path[count - 1][1];&lt;br /&gt;     } else {&lt;br /&gt;       done = true;&lt;br /&gt;      &lt;br /&gt;     }&lt;br /&gt;     if (!done) {&lt;br /&gt;       var col = HG.findPrimeInRow(path[count][0]);&lt;br /&gt;       count++;&lt;br /&gt;       path[count][0] = path[count - 1][0];&lt;br /&gt;       path[count][1] = col;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   HG.convertPath(path, count);&lt;br /&gt;&lt;br /&gt;   // Clear Covers&lt;br /&gt;   HG.initArray(HG.cCov,0);&lt;br /&gt;   HG.initArray(HG.rCov,0);&lt;br /&gt;   HG.erasePrimes();&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; findStarInCol: function(col) {&lt;br /&gt;   var retVal = -1;&lt;br /&gt;   for (var i = 0; i &amp;lt; HG.stars.length; i++) {&lt;br /&gt;     if (HG.stars[i][col] == 1) {&lt;br /&gt;       retVal = i;&lt;br /&gt;       i = HG.stars.length;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return retVal;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; findPrimeInRow: function(row) {&lt;br /&gt;   var retVal = -1;&lt;br /&gt;   for (var j=0; j&amp;lt; HG.stars[row].length; j++) {&lt;br /&gt;     if (HG.stars[row][j] == 2) {&lt;br /&gt;       retVal = j;&lt;br /&gt;       j = HG.stars[row].length;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return retVal;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; /* Should convert all primes to stars and reset all stars.&lt;br /&gt;  * Count is needed to be sure we look at all items in the path&lt;br /&gt;  */&lt;br /&gt; convertPath: function(path, count) {&lt;br /&gt;   HG.logMatrix(path, "Step 5: Converting Path.  Count = " + count);&lt;br /&gt;   for (var i=0; i &amp;lt; count+1; i++) {&lt;br /&gt;     var x = path[i][0];&lt;br /&gt;     var y = path[i][1];&lt;br /&gt;     if (HG.stars[x][y] == 1) {&lt;br /&gt;       HG.stars[x][y] = 0;&lt;br /&gt;     } else if (HG.stars[x][y] == 2) {&lt;br /&gt;       HG.stars[x][y] = 1;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; erasePrimes: function() {&lt;br /&gt;   for (var i=0; i&amp;lt;HG.stars.length; i++) {&lt;br /&gt;     for (var j=0; j &amp;lt; HG.stars[i].length; j++){&lt;br /&gt;       if (HG.stars[i][j] == 2) {&lt;br /&gt;         HG.stars[i][j] = 0;&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; findSmallestUncoveredVal: function(matrix) {&lt;br /&gt;   var min = Number.MAX_VALUE;&lt;br /&gt;   for (var i = 0; i &amp;lt; matrix.length; i++) {&lt;br /&gt;     for (var j = 0; j &amp;lt; matrix[i].length; j++) {&lt;br /&gt;       if (HG.rCov[i] == 0 &amp;amp;&amp;amp; HG.cCov[j] == 0) {&lt;br /&gt;         if (min &amp;gt; matrix[i][j]) {&lt;br /&gt;           min = matrix[i][j];&lt;br /&gt;         }&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return min;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; /**&lt;br /&gt;  * step 6&lt;br /&gt;  * modify the matrix&lt;br /&gt;  * if the row is covered, add the smallest value&lt;br /&gt;  * if the column is not covered, subtract the smallest value&lt;br /&gt;  */&lt;br /&gt; uncoverSmallest: function(smallest, matrix) {&lt;br /&gt;   TrU.log("Uncover Smallest: "+ smallest);&lt;br /&gt;       HG.logMatrix(matrix, "B4 Smallest uncovered");&lt;br /&gt;&lt;br /&gt;   for (var i = 0; i &amp;lt; matrix.length; i++) {&lt;br /&gt;     for (var j = 0; j &amp;lt; matrix[i].length; j++) {&lt;br /&gt;       if (HG.rCov[i] == 1) {&lt;br /&gt;         matrix[i][j] += smallest;&lt;br /&gt;       }&lt;br /&gt;       if (HG.cCov[j] == 0) {&lt;br /&gt;         matrix[i][j] -= smallest;&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   HG.logMatrix(matrix, "Smallest uncovered");&lt;br /&gt;   return matrix;&lt;br /&gt; },&lt;br /&gt;&lt;br /&gt; getSolution: function(formation, squad) {&lt;br /&gt;   var total = 0;&lt;br /&gt;   var lineup = [];&lt;br /&gt;    // Changed from length of stars, since we must ignore some rows due to padding.&lt;br /&gt;   for (var i = 0; i &amp;lt; HG.rows; i++) {&lt;br /&gt;     for (var j = 0; j &amp;lt; HG.cols; j++) {&lt;br /&gt;       if (HG.stars[i][j] == 1) {&lt;br /&gt;     /* the player (worker) at index j is the best player&lt;br /&gt;      * for poisition (job) at index i in your initial arrays.&lt;br /&gt;      */&lt;br /&gt;         lineup.push(getThePlayer(i,j));&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt;   return lineup;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function HgCoords() {&lt;br /&gt; this.row = -1;&lt;br /&gt; this.col = -1;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-5722192884309185557?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/k0DfhHtqR06a9pmXwj2bRdfj50g/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/k0DfhHtqR06a9pmXwj2bRdfj50g/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/k0DfhHtqR06a9pmXwj2bRdfj50g/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/k0DfhHtqR06a9pmXwj2bRdfj50g/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=4NPezH8n"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=EncrBnDY"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=gtxc2bbM"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=gtxc2bbM" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=96rfFEnN"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=96rfFEnN" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=qoFogB50"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=qoFogB50" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/A30UZN1_dLs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/5722192884309185557/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=5722192884309185557" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/5722192884309185557?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/5722192884309185557?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/A30UZN1_dLs/hungarian-algorithm-in-javascript.html" title="The Hungarian Algorithm in Javascript" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/01/hungarian-algorithm-in-javascript.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08HSX8-eyp7ImA9WxVSEko.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-1882139600430842149</id><published>2009-01-06T14:54:00.002-05:00</published><updated>2009-01-06T14:57:18.153-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-06T14:57:18.153-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="trophymanager" /><category scheme="http://www.blogger.com/atom/ns#" term="TrExMa" /><title>New TrExMa Preview Release</title><content type="html">I've updated TrExMa for Firefox after finding a bug in scraping the Transfer Pages.&lt;br /&gt;&lt;br /&gt;This bug exists in all versions of the plugin as the Transfer List uses slightly different HTML to display starred skills.  The starred skills were getting skipped over and treated as zero.  Sheffield FC also picked up on this bug and reported to me as I was in the midst of adding some additional features for this release.  So thanks to him for noticing the problem!&lt;br /&gt;&lt;br /&gt;Additional features include the introduction of skill summaries, Attack, Defending, etc.&lt;br /&gt;&lt;br /&gt;You can find the new plugin: http://code.google.com/p/trexma-for-firefox/&lt;br /&gt;&lt;br /&gt;Thanks!&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-1882139600430842149?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/l8_C0gfElNpphqBXtRs23Ub5BTI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/l8_C0gfElNpphqBXtRs23Ub5BTI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/l8_C0gfElNpphqBXtRs23Ub5BTI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/l8_C0gfElNpphqBXtRs23Ub5BTI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=rEVEHzL6"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=5yMTJ9FM"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=Tf3dVsAf"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=Tf3dVsAf" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=Si40EINc"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=Si40EINc" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=2DILe3P3"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=2DILe3P3" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/-aJgeowLPXY" height="1" width="1"/&gt;</content><link rel="related" href="http://code.google.com/p/trexma-for-firefox/" title="New TrExMa Preview Release" /><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/1882139600430842149/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=1882139600430842149" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/1882139600430842149?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/1882139600430842149?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/-aJgeowLPXY/new-trexma-preview-release.html" title="New TrExMa Preview Release" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2009/01/new-trexma-preview-release.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04GSX48fip7ImA9WxRaFU4.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-5859120607440228450</id><published>2008-12-17T13:39:00.003-05:00</published><updated>2008-12-17T13:52:08.076-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-17T13:52:08.076-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="trophymanager" /><category scheme="http://www.blogger.com/atom/ns#" term="TrExMa" /><category scheme="http://www.blogger.com/atom/ns#" term="jQuery" /><title>TrExMa for Firefox 0.8 Preview Release</title><content type="html">Over at &lt;a href="http://code.google.com/p/trexma-for-firefox/"&gt;TrExMa For Firefox&lt;/a&gt;, you can find a long awaited update to the TrExMa plugin for the popular TrophyManager game.  Version 0.8 is available and this is a Preview Release.&lt;br /&gt;&lt;br /&gt;This was an excuse to learn a little about &lt;a href="http://www.mozilla.org/projects/xul/"&gt;XUL&lt;/a&gt;, do some jQuery and refactor the plugin.  Unfortunately, it still needs a ton of refactoring, and the code is quite soupy.  This is a use at your own risk version.  The plugin can crash, provide a little popup saying something's wrong, and sometimes not even offer that.  It's buggy.  Since I'm not a XUL or UI expert, there's a lot of things that have been done in a sloppy fashion and things that cause bugs.  Anyone who's using this, I would really appreciate using the &lt;a href="http://code.google.com/p/trexma-for-firefox/issues/list"&gt;issue tracker&lt;/a&gt; rather than sending an email over at TM or posting in those forums.  Reason is, I get notified if someone posts an issue. &lt;br /&gt;&lt;br /&gt;It allows the user to see hit players abilities for all positions and dynamically change the loss of skill for the player being out of a favorite position.  This feature works by clicking on the TrExMa for Firefox label in the status bar.  You'll see a little XUL window appear in your browser.  If you browse to a squad screen or a transfer list screen, you'll get a list of players to choose from.  Clicking on a player will present the skills for that player in all positions available. &lt;br /&gt;&lt;br /&gt;In addition, a drop down box is available to determine what player is the best at each position.  If you want to find the best ML, select ML and the plugin will produce the top 5 players on that screen in that particular position.&lt;br /&gt;&lt;br /&gt;Quite a bit of refactoring involved brining in the &lt;a href="http://www.jquery.com"&gt;jQuery&lt;/a&gt; Javascript plugin.  I'm very happy with the integration of jQuery, it's an outstanding tool, as it just flat out works against HTML and XUL.&lt;br /&gt;&lt;br /&gt;If you don't like it, uninstall it and reinstall 0.6.2.  If you do like it, please offer suggestions and features that you'd like to see.  Still on the idea block is the ability to determine your best 11 for a given formation, but that's a little ways off.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-5859120607440228450?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/UKv9O9ml9lwhg5WL-cyQSTLH1F4/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/UKv9O9ml9lwhg5WL-cyQSTLH1F4/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/UKv9O9ml9lwhg5WL-cyQSTLH1F4/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/UKv9O9ml9lwhg5WL-cyQSTLH1F4/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=QkNIU1cH"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=PXsDxbck"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=7RzdQs9U"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=7RzdQs9U" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=GjcdxWgq"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=GjcdxWgq" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=o6ozJp7o"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=o6ozJp7o" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/oBJNs_dtpz8" height="1" width="1"/&gt;</content><link rel="related" href="http://code.google.com/p/trexma-for-firefox/" title="TrExMa for Firefox 0.8 Preview Release" /><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/5859120607440228450/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=5859120607440228450" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/5859120607440228450?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/5859120607440228450?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/oBJNs_dtpz8/trexma-for-firefox-08-preview-release.html" title="TrExMa for Firefox 0.8 Preview Release" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2008/12/trexma-for-firefox-08-preview-release.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkYFRHs8eyp7ImA9WxRbGEk.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-7707897604137869260</id><published>2008-12-09T12:00:00.007-05:00</published><updated>2008-12-09T12:35:15.573-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-12-09T12:35:15.573-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Woot-Off" /><category scheme="http://www.blogger.com/atom/ns#" term="Javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="Woot" /><category scheme="http://www.blogger.com/atom/ns#" term="Spry" /><title>A Spry Woot-Off Tracker</title><content type="html">Although &lt;a href="http://www.adobe.com/products/flex/"&gt;Flex&lt;/a&gt; is a neat tool, good ol' Javascript has been around for quite a while, and I thought I'd try to build a &lt;a href="http://www.woot.com"&gt;Woot-Off&lt;/a&gt; tracker using Flex's Javascript cousin, &lt;a href="http://labs.adobe.com/technologies/spry/home.html"&gt;Spry&lt;/a&gt;. Conveniently, there's a Woot-Off going on today.&lt;br /&gt;&lt;br /&gt;Spry is an interesting Javascript toolkit since it focuses on data extraction and presentation and widgets.  In this example, we're not using any widgets, but we are taking advantage of the Spry Dataset tools to grok the XML stream from Woot.  Just like with the AIR version, we need to use a proxy to grab the Woot XML stream.&lt;br /&gt;&lt;br /&gt;Spry's &lt;a href="http://labs.adobe.com/technologies/spry/articles/data_set_overview/index.html"&gt;DataSet&lt;/a&gt; works by allowing the developer to query the data set. Since Woot is providing an RSSish feed, we use the XMLDataSet class. By using braces, the dataset's contents can be accessed using the name of the XML tag.  So to grab the price, we use {woot:price}.  It's relatively simple.&lt;br /&gt;&lt;br /&gt;The challenging part is for data that's not quite perfectly formatted.  In this case, the percentage needs to be multiplied by 100.  We do that inside the &lt;a href="http://labs.adobe.com/technologies/spry/samples/data_region/DataSetObserver.html"&gt;Observer&lt;/a&gt;.  The Observer can update the contents of the dataset.  So you can simply create an observer function and change away.  The description also needs to be cleaned up since its HTML entities do not provide the needed effect.  We actually want to use the tags, so we unentify them.&lt;br /&gt;&lt;br /&gt;The timer is actually easier than in Flex, since the timer comes automatically with the DataSet with the loadInterval option.  The only thing we need to do is speed it up and slow it down at the appropriate time.&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&amp;gt;&lt;br /&gt;&amp;lt;html xmlns="http://www.w3.org/1999/xhtml" xmlns:spry="http://ns.adobe.com/spry"&amp;gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt; &amp;lt;meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" /&amp;gt;&lt;br /&gt; &amp;lt;title&amp;gt;Spry Woot Tracker&amp;lt;/title&amp;gt;&lt;br /&gt; &amp;lt;link type="text/css" rel="stylesheet" href="http://yui.yahooapis.com/2.6.0/build/reset-fonts-grids/reset-fonts-grids.css"/&amp;gt;&lt;br /&gt; &amp;lt;link href="css/wootTracker.css" rel="stylesheet" type="text/css" /&amp;gt;&lt;br /&gt; &amp;lt;script type="text/javascript" src="includes/xpath.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;script type="text/javascript" src="includes/SpryData.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;   // we don't want to use cached data, and we need to reload every 30 seconds&lt;br /&gt;  var dsWootInfo = new Spry.Data.XMLDataSet("WootProxy", "rss/channel/item", {useCache: false, loadInterval: 30000});&lt;br /&gt;&lt;br /&gt;   var quickCheck = false; // Is the timer sped up&lt;br /&gt;&lt;br /&gt;   // Observer watches for when data changes and modifies for presentation&lt;br /&gt;   function wootObserver(notificationType, dataSet) {&lt;br /&gt;    if (notificationType == "onDataChanged") {&lt;br /&gt;       if (dataSet) {&lt;br /&gt;         var data = dataSet.getData();&lt;br /&gt;         var soldout = data[0]["woot:soldoutpercentage"];&lt;br /&gt;         data[0]["woot:soldoutpercentage"] = soldout*100;&lt;br /&gt;         var desc = data[0]["description"];&lt;br /&gt;         // Description contains HTML entities, fix them&lt;br /&gt;         // Something strange is going on with the formatter  it's just desc not descdesc&lt;br /&gt;         desc = desc.replace(/&amp;amp;gt;/g, "&amp;gt;");&lt;br /&gt;         desc = desc.replace(/&amp;amp;lt;/g, "&amp;lt;");&lt;br /&gt;         desc = desc.replace(/&amp;amp;quot;/g, '"');&lt;br /&gt;         desc = desc.replace(/[\u201C\u201d]/g, '"');&lt;br /&gt;         data[0]["description"] =  desc ;&lt;br /&gt;&lt;br /&gt;         // Determine if it's time to speed up or slow down&lt;br /&gt;         if (quickCheck) {&lt;br /&gt;           if (soldout &amp;lt; .95) {&lt;br /&gt;             dsWootInfo.stopLoadInterval();&lt;br /&gt;             dsWootInfo.startLoadInterval(30000);&lt;br /&gt;             quickCheck = false;&lt;br /&gt;           }&lt;br /&gt;         } else {&lt;br /&gt;           if (soldout &amp;gt; .95) {&lt;br /&gt;             dsWootInfo.stopLoadInterval();&lt;br /&gt;             dsWootInfo.startLoadInterval(1000);&lt;br /&gt;             quickCheck = true;&lt;br /&gt;           }&lt;br /&gt;         }&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;   };&lt;br /&gt;&lt;br /&gt;   dsWootInfo.addObserver(wootObserver);&lt;br /&gt;&lt;br /&gt; &amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;br /&gt;&amp;lt;body id="wootTracker"&amp;gt;&lt;br /&gt; &amp;lt;noscript&amp;gt;&lt;br /&gt;  &amp;lt;h1&amp;gt;This page requires JavaScript. Please enable JavaScript in your browser and reload this page.&amp;lt;/h1&amp;gt;&lt;br /&gt; &amp;lt;/noscript&amp;gt;&lt;br /&gt; &amp;lt;div id="doc" spry:region="dsWootInfo"&amp;gt;&lt;br /&gt;  &amp;lt;h1 id="wootName"&amp;gt; {title} &amp;lt;/h1&amp;gt;&lt;br /&gt;   &amp;lt;div style="float:left; padding-right: 10px"&amp;gt;&lt;br /&gt;     &amp;lt;img src="{woot:thumbnailimage}" /&amp;gt;&lt;br /&gt;     &amp;lt;h3 id="wootPrice"&amp;gt;{woot:price} &amp;lt;/h3&amp;gt;&lt;br /&gt;     &amp;lt;h4&amp;gt;&amp;lt;a href="{woot:purchaseurl}" target="_blank"&amp;gt;Buy This Woot&amp;lt;/a&amp;gt;&amp;lt;/h4&amp;gt;&lt;br /&gt;     &amp;lt;h3 id="wootPercent"&amp;gt;{woot:soldoutpercentage}% Sold Thus Far&amp;lt;/h3&amp;gt;&lt;br /&gt;   &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;div id="description" &amp;gt;{description}&amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And here's the CSS:&lt;br /&gt;&lt;pre name="code" class="css"&gt;&lt;br /&gt;body {&lt;br /&gt; background:#EDEDED none repeat scroll 0 0;&lt;br /&gt; color:#191919;&lt;br /&gt; margin:0;&lt;br /&gt; padding:0;&lt;br /&gt;}&lt;br /&gt;h1 { font-size: 182%; margin-bottom: 0.5em}&lt;br /&gt;h3 {font-size:138.5%; margin-bottom: 0.5em}&lt;br /&gt;h4 {font-size:123.1%; margin-bottom: 0.5em}&lt;br /&gt;li {list-style-type:disc; list-style-position:inside}&lt;br /&gt;strong {font-weight:bold}&lt;br /&gt;p {margin-top: 1em}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This application can be run inside Tomcat or wherever you have a proxy running.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-7707897604137869260?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/rQ_7TH8mJQbE9kFZhyZhU_VetuQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/rQ_7TH8mJQbE9kFZhyZhU_VetuQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/rQ_7TH8mJQbE9kFZhyZhU_VetuQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/rQ_7TH8mJQbE9kFZhyZhU_VetuQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=Abfkcopv"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=wwIoqmxq"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=jMGTYCWi"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=jMGTYCWi" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=UtjWRokl"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=UtjWRokl" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=uaME6r4C"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=uaME6r4C" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/zGlKDrCBviI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/7707897604137869260/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=7707897604137869260" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/7707897604137869260?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/7707897604137869260?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/zGlKDrCBviI/spry-woot-off-tracker.html" title="A Spry Woot-Off Tracker" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2008/12/spry-woot-off-tracker.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D04BQn0yfCp7ImA9WxRUFU4.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-942019086080662665</id><published>2008-11-24T08:21:00.003-05:00</published><updated>2008-11-24T09:12:33.394-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-24T09:12:33.394-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SAP Portal" /><category scheme="http://www.blogger.com/atom/ns#" term="Google Analytics" /><category scheme="http://www.blogger.com/atom/ns#" term="jQuery" /><title>More Google Analytics in SAP Portal with jQuery</title><content type="html">One of the challenges with &lt;a href="http://twofourone.blogspot.com/2008/07/enhanced-google-analytics-in-sap-portal.html"&gt;SAP Portal and integrating Google Analytics&lt;/a&gt; is it's tendency to create a lot of links that pop content open in a new window.  Since you don't have access to the code that creates these URLs it causes a wee bit of a headache when you look to determine what items are being clicked on in the KM, or where your users are linking out of the portal to certain other applications.&lt;br /&gt;&lt;br /&gt;We can resolve some of this by using a javascript library to scrape the HTML page and insert some onclick events that will allow the items to be tracked.&lt;br /&gt;&lt;br /&gt;How can we accomplish this?&lt;br /&gt;&lt;br /&gt;There are two steps:&lt;br /&gt;&lt;br /&gt;First, add access to your favorite javascript library inside the Google Analytics code.   I've chosen &lt;a href="http://www.jquery.com"&gt;jQuery&lt;/a&gt;, although you could easily use other libraries.  You can do this through the ga-split-1.js file that was &lt;a href="http://twofourone.blogspot.com/2008/06/sap-portal-and-google-analytics.html"&gt;outlined earlier&lt;/a&gt;. Don't forget to change the name of the file if need be so it is not cached in users browsers.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="jscript"&gt;&lt;br /&gt;var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");&lt;br /&gt;document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));&lt;br /&gt;document.write(unescape("%3Cscript src='http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js' type='text/javascript'%3E%3C/script%3E"));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;By pulling jQuery from Google, we're increasing our load time, since we won't have to wait on connections to the Portal. The risk is low, since it's Google.  In addition, if jQuery isn't found, we just won't track certain types of links.  We'd still get the key pack clicking information.&lt;br /&gt;&lt;br /&gt;The second part is to actually use jQuery to track stuff.  You might be able to use the &lt;a href="http://plugins.jquery.com/project/gaTracker"&gt;jQuery GA &lt;/a&gt;plugin, but in my case, I decided to write my own javascript based upon the plugin to do the trick.  I would keep this code in a separate file and load it after you've initialized the pageTracker within your PortalComponent:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="jscript"&gt;&lt;br /&gt;function ga_decorateLink(u){&lt;br /&gt; var trackingURL = '';&lt;br /&gt; if(u.indexOf('://') == -1 &amp;amp;&amp;amp; u.indexOf('mailto:') != 0){&lt;br /&gt;  // no protocol or mailto - internal link - check extension&lt;br /&gt;  var ext = u.split('.')[u.split('.').length - 1];   &lt;br /&gt;  var exts = ['pdf','doc','xls','csv','jpg','gif', 'mp3','swf','txt','ppt','zip','gz','dmg','xml'] &lt;br /&gt;  for(i = 0; i &amp;lt; exts.length; i++){&lt;br /&gt;   if(ext == exts[i]){&lt;br /&gt;    // Likely grabbing an item from KM, etc.&lt;br /&gt;    trackingURL = '/downloads/' + u;&lt;br /&gt;    break;&lt;br /&gt;   }&lt;br /&gt;  }    &lt;br /&gt; } else {&lt;br /&gt;  if(u.indexOf('mailto:') == 0){&lt;br /&gt;   // mailto link - decorate&lt;br /&gt;   trackingURL = '/mailto/' + u.substring(7);     &lt;br /&gt;  } else {&lt;br /&gt;   // complete URL - check domain&lt;br /&gt;   var regex = /([^:\/]+)*(?::\/\/)*([^:\/]+)(:[0-9]+)*\/?/i;&lt;br /&gt;   var linkparts = regex.exec(u);&lt;br /&gt;   var urlparts = regex.exec(location.href);     &lt;br /&gt;   if(linkparts[2] != urlparts[2]) trackingURL = '/external/' + u; /*leaving the portal*/&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; return trackingURL;   &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Since you've initialized pageTracker in each Portal page, we're skipping that here.&lt;br /&gt;// just wait until the entire page loads&lt;br /&gt;$(document).ready(function(){&lt;br /&gt; $('a').each(function(){&lt;br /&gt;  var u = $(this).attr('href');&lt;br /&gt;   &lt;br /&gt;  if(typeof(u) != 'undefined'){&lt;br /&gt;   var newLink = decorateLink(u);&lt;br /&gt;   if(newLink.length){&lt;br /&gt;    $(this).click(function(){&lt;br /&gt;     $.pageTracker._trackPageview(newLink);&lt;br /&gt;    });&lt;br /&gt;   }&lt;br /&gt;  }    &lt;br /&gt; });&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you're using the defualt framework, be aware that you will not be able to track each and every link.  Javascript cannot dive into iframes on the page. Since it can't do that, you'll be unable to track each and every link, unless you can use embedded for that particular iView which will eliminate the iframes.&lt;br /&gt;&lt;br /&gt;Some fair warning here. This is from memory. I am no longer working with SAP Portal, so there is a good chance I've forgotten something here.  However, it did work on my last day working with Portal...at least the version at that gig. If you run into problems, please fix them and share them.  Don't hold onto it.  Share it with the rest of the SAP community, post it on your blog, or submit it to SDN for inclusion in their hosted materials. At the very least, post a solution in the SDN forums so that others can use this.  When you get it working, it's pretty darned cool!&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-942019086080662665?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/xEtWyvrSnjH7K25_eyQzez3ToDo/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/xEtWyvrSnjH7K25_eyQzez3ToDo/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/xEtWyvrSnjH7K25_eyQzez3ToDo/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/xEtWyvrSnjH7K25_eyQzez3ToDo/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=fz2PINIU"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=rVaTqdFM"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=ApFTQhC0"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=ApFTQhC0" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=MehhUPBf"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=MehhUPBf" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=jXY5Xeil"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=jXY5Xeil" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/ySUTHC9zAxQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/942019086080662665/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=942019086080662665" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/942019086080662665?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/942019086080662665?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/ySUTHC9zAxQ/more-google-analytics-in-sap-portal.html" title="More Google Analytics in SAP Portal with jQuery" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2008/11/more-google-analytics-in-sap-portal.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYDQ3c-fSp7ImA9WxRUEkU.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-2166067587013280617</id><published>2008-11-21T08:53:00.007-05:00</published><updated>2008-11-21T10:42:52.955-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-21T10:42:52.955-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Woot-Off" /><category scheme="http://www.blogger.com/atom/ns#" term="AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="AIR Proxy" /><category scheme="http://www.blogger.com/atom/ns#" term="Woot" /><category scheme="http://www.blogger.com/atom/ns#" term="Flex" /><title>An AIR based Woot-Off tracker</title><content type="html">I've been going through some &lt;a href="http://www.adobe.com/products/flex/"&gt;Flex&lt;/a&gt; training this week.  It's an interesting tool, and pretty easy to make a quick application.  Unfortunately, the training has been a bit robotic in terms of being very prescriptive on how to perform somewhat elementary programming.  So it was time to take a break and actually try to attempt something that would be useful....or at least somewhat useful.&lt;br /&gt;&lt;br /&gt;Since we had a &lt;a href="http://www.woot.com/WhatIsWoot.aspx#q9"&gt;Woot-Off&lt;/a&gt; yesterday, I decided to use Flex to write a Woot-Off tracker.  A handy little &lt;a href="http://www.adobe.com/products/air/"&gt;AIR&lt;/a&gt; application to see when a new item appears.  It's a simple single windowed application that polls &lt;a href="http://www.woot.com/Blog/ViewEntry.aspx?Id=2118"&gt;Woot's API&lt;/a&gt; every 30 seconds, groks the RSSish feed, and displays information about the item being sold.&lt;br /&gt;&lt;br /&gt;In addition, it provides handy information regarding the status of the sale in terms of percentage of items sold and a button to purchase the product, or manually check Woot for an update.  If the percentage sold is above 94%, it ramps up the polling process to check Woot every second, since you never know when the &lt;a href="http://www.woot.com/Blog/ViewEntry.aspx?Id=1623"&gt;BOC&lt;/a&gt; will appear.&lt;br /&gt;&lt;br /&gt;The application is hardly complete.  It lacks any style or substance in terms of look and feel.  It also neglects the ability to run in the system tray (ala &lt;a href="http://www.twhirl.org/"&gt;Twhirl&lt;/a&gt; or &lt;a href="http://www.tweetdeck.com/"&gt;Tweetdeck&lt;/a&gt;) and update the user that an item might be selling out soon or that a new item is available.  Right now it simply runs on the screen.&lt;br /&gt;&lt;br /&gt;Of course, the trickiest part of this application is the need to run a Proxy service to hit an external URL.  Due to Flash's security model, and the lack of a &lt;a href="http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_14213"&gt;crossdomain.xml&lt;/a&gt; file at Woot, you need to have a local service running that will act as a proxy.  A quick Java servlet and the very very lightweight &lt;a href="http://winstone.sourceforge.net/"&gt;Winstone&lt;/a&gt; servlet container.  Ideally, you would launch this app with a little batch script that spun up your Servlet based proxy and then spun up the AIR app.&lt;br /&gt;&lt;br /&gt;So let's walk through the source.  That way, all of you out there who've actually done a lot of Flex and look at this and let me know what a BOC it is. :D  First we'll look at the AIR app, and finally the Java based Proxy.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="AS3"&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;br /&gt;&amp;lt;mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"&lt;br /&gt;creationComplete="init()" width="500" height="370" xmlns:utils="flash.utils.*"&amp;gt;&lt;br /&gt;&amp;lt;mx:Script&amp;gt;&lt;br /&gt; &amp;lt;![CDATA[&lt;br /&gt;  import flash.net.navigateToURL;&lt;br /&gt;  import flash.utils.Timer;&lt;br /&gt;  import mx.formatters.Formatter;&lt;br /&gt;  import mx.formatters.NumberFormatter;&lt;br /&gt;  import mx.collections.ArrayCollection;&lt;br /&gt;  import mx.controls.Image;&lt;br /&gt;  import mx.rpc.events.ResultEvent;&lt;br /&gt;&lt;br /&gt;  private var START_QUICK_POLL_PERCENT:Number = 0.94;&lt;br /&gt;&lt;br /&gt;  // a 30 second and a 1 second Timer&lt;br /&gt;  private var wootPing:Timer = new Timer(30000, 1000000000);&lt;br /&gt;  private var wootEndPing:Timer = new Timer(1000, 1000000000);&lt;br /&gt;  private var checks:int = 0;&lt;br /&gt;&lt;br /&gt;  [Bindable] private var wootItemText:String = "No Woot Found...Yet";&lt;br /&gt;  [Bindable] private var wootItemPrice:String = "$10,000,000.00";&lt;br /&gt;  [Bindable] private var wootItemPercent:String = "0% Sold";&lt;br /&gt;  [Bindable] private var wootItemLink:String = "http://www.woot.com";&lt;br /&gt;  [Bindable] private var checkText:String= "Checked\n0 Times";&lt;br /&gt;  [Bindable] private var itemImgURL:String = "";&lt;br /&gt;&lt;br /&gt;  // Setup the Timers, and start the default timer &lt;br /&gt;  private function init():void {&lt;br /&gt;   wootPing.addEventListener(Event.ACTIVATE, wootHandler);&lt;br /&gt;   wootPing.addEventListener(TimerEvent.TIMER, wootHandler);&lt;br /&gt;   wootPing.start();&lt;br /&gt;   wootEndPing.addEventListener(TimerEvent.TIMER, wootHandler);&lt;br /&gt;  }  &lt;br /&gt; &lt;br /&gt;  // Handles the purchase button to open your browser&lt;br /&gt;  private function openWootWindow(event:MouseEvent):void {&lt;br /&gt;   var u:URLRequest = new URLRequest(wootItemLink);&lt;br /&gt;   flash.net.navigateToURL(u, "_blank");&lt;br /&gt;  }&lt;br /&gt; &lt;br /&gt;  // Generally use the Event to handle updating the app &lt;br /&gt;  private function wootHandler(event:Event):void {&lt;br /&gt;   getWoot();&lt;br /&gt;  }&lt;br /&gt; &lt;br /&gt;  // Hits the API.    &lt;br /&gt;  private function getWoot():void {&lt;br /&gt;   wootService.send();&lt;br /&gt;   checkCount.text = "Checked\n"+ ++checks + " times";&lt;br /&gt;  }&lt;br /&gt; &lt;br /&gt;  // Updates all of the items when the HTTPService completes&lt;br /&gt;  private function wootResultHandler(event:ResultEvent):void {&lt;br /&gt;   wootItemText = wootService.lastResult.rss.channel.item.title;&lt;br /&gt;   wootItemPrice = wootService.lastResult.rss.channel.item.price;&lt;br /&gt;   wootItemLink = wootService.lastResult.rss.channel.item.purchaseurl;&lt;br /&gt;   wootDesc.htmlText = wootService.lastResult.rss.channel.item.description;&lt;br /&gt;   wootImage.source =  wootService.lastResult.rss.channel.item.thumbnailimage;&lt;br /&gt;   // Determine if we need to do percentage checking.&lt;br /&gt;   if (!(new Boolean(wootService.lastResult.rss.channel.item.wootoff))) {&lt;br /&gt;    wootItemPercent = "This is not a Woot-Off";&lt;br /&gt;   } else {&lt;br /&gt;    var percentNum:Number = new Number(wootService.lastResult.rss.channel.item.soldoutpercentage);&lt;br /&gt;    wootItemPercent = new String(percentNum/100 + "% Sold");&lt;br /&gt;    // Do we need to start checking more often?&lt;br /&gt;    if (!wootEndPing.running &amp;amp;&amp;amp; percentNum &amp;gt; START_QUICK_POLL_PERCENT) {&lt;br /&gt;     wootEndPing.start();&lt;br /&gt;    } else if (wootEndPing.running &amp;amp;&amp;amp; percentNum &amp;lt; START_QUICK_POLL_PERCENT) {&lt;br /&gt;     wootEndPing.stop();&lt;br /&gt;    }&lt;br /&gt;   } &lt;br /&gt;  }&lt;br /&gt; ]]&amp;gt;&lt;br /&gt;&amp;lt;/mx:Script&amp;gt;&lt;br /&gt;&amp;lt;mx:HTTPService url="http://localhost:8080/WootProxy"&lt;br /&gt; id="wootService" result="wootResultHandler(event)" /&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;mx:VBox left="5" right="5" top="5" bottom="5"&amp;gt;&lt;br /&gt; &amp;lt;mx:Label id="itemText" text="{wootItemText}" fontSize="12" fontWeight="bold"/&amp;gt;&lt;br /&gt; &amp;lt;mx:Canvas width="100%"&amp;gt;&lt;br /&gt;  &amp;lt;mx:Button toolTip="Click to purchase" label="Purchase" click="openWootWindow(event)" y="152" x="0"/&amp;gt;&lt;br /&gt;  &amp;lt;mx:TextArea height="300" id="wootDesc" left="150" right="0" /&amp;gt;&lt;br /&gt;  &amp;lt;mx:Image id="wootImage" width="142" height="116" left="0" top="0" &amp;gt;&lt;br /&gt;   &amp;lt;mx:source&amp;gt;http://upload.wikimedia.org/wikipedia/en/1/16/Wootlogo.png&amp;lt;/mx:source&amp;gt;&lt;br /&gt;  &amp;lt;/mx:Image&amp;gt;&lt;br /&gt;  &amp;lt;mx:Label id="checkCount" text="{checkText}" x="0" y="212" height="52" width="142"/&amp;gt;&lt;br /&gt;  &amp;lt;mx:Button toolTip="Click to load Woot" label="Check" click="getWoot()" y="182" /&amp;gt;&lt;br /&gt;  &amp;lt;mx:Label id="itemPrice" text="{wootItemPrice}" y="124" fontStyle="italic" fontSize="12" x="0"  width="142"/&amp;gt;&lt;br /&gt;  &amp;lt;mx:Label id="itemPercent" text="{wootItemPercent}" y="272" x="0"  width="142"/&amp;gt;&lt;br /&gt; &amp;lt;/mx:Canvas&amp;gt;&lt;br /&gt;&amp;lt;/mx:VBox&amp;gt;&lt;br /&gt;&amp;lt;/mx:WindowedApplication&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And finally the Proxy:&lt;br /&gt;&lt;pre name="code" class="Java"&gt;&lt;br /&gt;/*&lt;br /&gt;* To change this template, choose Tools | Templates&lt;br /&gt;* and open the template in the editor.&lt;br /&gt;*/&lt;br /&gt;package org.woot.tracker;&lt;br /&gt;&lt;br /&gt;import java.io.BufferedReader;&lt;br /&gt;import java.io.DataInputStream;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.io.InputStreamReader;&lt;br /&gt;import java.io.OutputStream;&lt;br /&gt;import java.io.PrintWriter;&lt;br /&gt;import java.net.HttpURLConnection;&lt;br /&gt;import java.net.MalformedURLException;&lt;br /&gt;import java.net.URL;&lt;br /&gt;import javax.servlet.ServletException;&lt;br /&gt;import javax.servlet.http.HttpServlet;&lt;br /&gt;import javax.servlet.http.HttpServletRequest;&lt;br /&gt;import javax.servlet.http.HttpServletResponse;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;*&lt;br /&gt;* @author student&lt;br /&gt;*/&lt;br /&gt;public class WootProxy extends HttpServlet {&lt;br /&gt;&lt;br /&gt;static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Processes requests for both HTTP &amp;lt;code&amp;gt;GET&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;POST&amp;lt;/code&amp;gt; methods.&lt;br /&gt; * @param request servlet request&lt;br /&gt; * @param response servlet response&lt;br /&gt; * @throws ServletException if a servlet-specific error occurs&lt;br /&gt; * @throws IOException if an I/O error occurs&lt;br /&gt; */&lt;br /&gt;protected void processRequest(HttpServletRequest request, HttpServletResponse response)&lt;br /&gt;    throws ServletException, IOException {&lt;br /&gt;&lt;br /&gt;  String contentObj = "http://www.woot.com/salerss.aspx";&lt;br /&gt;&lt;br /&gt;  URL content = null;&lt;br /&gt;&lt;br /&gt;  if (null == contentObj) {&lt;br /&gt;    throw new ServletException("The destination url must be specified for ProxyHttpService");&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;    content = new URL(contentObj);&lt;br /&gt;  } catch (MalformedURLException e) {&lt;br /&gt;    throw new ServletException(contentObj + " is a malformed url.");&lt;br /&gt;  }&lt;br /&gt;  HttpURLConnection contentCon = null;&lt;br /&gt;  try {&lt;br /&gt;    contentCon = (HttpURLConnection) content.openConnection();&lt;br /&gt;  } catch (IOException exception) {&lt;br /&gt;    throw new ServletException("Problem opening " + contentObj + ": " + exception.toString());&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  // Get the content type from the URLConnection and set it on the response.&lt;br /&gt;  String contentType = contentCon.getContentType();&lt;br /&gt;  response.setContentType(contentType);&lt;br /&gt;&lt;br /&gt;  // Get and read the input stream.&lt;br /&gt;  StringBuffer buffer = new StringBuffer();&lt;br /&gt;&lt;br /&gt;  BufferedReader din =&lt;br /&gt;      new BufferedReader(new InputStreamReader(contentCon.getInputStream()));&lt;br /&gt;&lt;br /&gt;  String s;&lt;br /&gt;  while ((s = din.readLine()) != null) {&lt;br /&gt;    buffer.append(s);&lt;br /&gt;  }&lt;br /&gt;  din.close();&lt;br /&gt;&lt;br /&gt;  // Now write the bytes out to the client.&lt;br /&gt;  byte[] contentBytes = buffer.toString().getBytes();&lt;br /&gt;  OutputStream out = response.getOutputStream();&lt;br /&gt;  out.write(contentBytes, 0, contentBytes.length);&lt;br /&gt;  out.flush();&lt;br /&gt;  out.close();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// &amp;lt;editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code."&amp;gt;&lt;br /&gt;/**&lt;br /&gt; * Handles the HTTP &amp;lt;code&amp;gt;GET&amp;lt;/code&amp;gt; method.&lt;br /&gt; * @param request servlet request&lt;br /&gt; * @param response servlet response&lt;br /&gt; * @throws ServletException if a servlet-specific error occurs&lt;br /&gt; * @throws IOException if an I/O error occurs&lt;br /&gt; */&lt;br /&gt;@Override&lt;br /&gt;protected void doGet(HttpServletRequest request, HttpServletResponse response)&lt;br /&gt;    throws ServletException, IOException {&lt;br /&gt;  processRequest(request, response);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Handles the HTTP &amp;lt;code&amp;gt;POST&amp;lt;/code&amp;gt; method.&lt;br /&gt; * @param request servlet request&lt;br /&gt; * @param response servlet response&lt;br /&gt; * @throws ServletException if a servlet-specific error occurs&lt;br /&gt; * @throws IOException if an I/O error occurs&lt;br /&gt; */&lt;br /&gt;@Override&lt;br /&gt;protected void doPost(HttpServletRequest request, HttpServletResponse response)&lt;br /&gt;    throws ServletException, IOException {&lt;br /&gt;  processRequest(request, response);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * Returns a short description of the servlet.&lt;br /&gt; * @return a String containing servlet description&lt;br /&gt; */&lt;br /&gt;@Override&lt;br /&gt;public String getServletInfo() {&lt;br /&gt;  return "Short description";&lt;br /&gt;}// &amp;lt;/editor-fold&amp;gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;There it is, enjoy :)&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-2166067587013280617?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/FXMJNwTknKHts9r4roDQy6s5eZE/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/FXMJNwTknKHts9r4roDQy6s5eZE/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/FXMJNwTknKHts9r4roDQy6s5eZE/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/FXMJNwTknKHts9r4roDQy6s5eZE/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=ZmvjeMFR"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=WnAMlNYv"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=HqxI0WxH"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=HqxI0WxH" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=kli7yCtd"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=kli7yCtd" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=eymIpM2m"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=eymIpM2m" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/RDv2MAvmFFM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/2166067587013280617/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=2166067587013280617" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/2166067587013280617?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/2166067587013280617?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/RDv2MAvmFFM/air-based-woot-off-tracker.html" title="An AIR based Woot-Off tracker" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2008/11/air-based-woot-off-tracker.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEAHQn0-fCp7ImA9WxRQGUg.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-45747871293083749</id><published>2008-10-13T22:50:00.002-04:00</published><updated>2008-10-13T22:52:13.354-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-13T22:52:13.354-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="trophymanager" /><category scheme="http://www.blogger.com/atom/ns#" term="TrExMa" /><title>TrExMa for Firefox 0.6.2</title><content type="html">TrophyManager.com players.  TrExMa 0.6.2 is finally available.  It should resolve most of the issues in getting a TrExMa rating since the update of TM's web site.&lt;br /&gt;&lt;br /&gt;http://code.google.com/p/trexma-for-firefox/&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-45747871293083749?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/wbiJ5jysfqC50s71xCQZNNML8Dk/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/wbiJ5jysfqC50s71xCQZNNML8Dk/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/wbiJ5jysfqC50s71xCQZNNML8Dk/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/wbiJ5jysfqC50s71xCQZNNML8Dk/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=h8slqKZr"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=mVoY3sGF"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=mhqSVEBk"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=mhqSVEBk" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=6jAhRIBE"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=6jAhRIBE" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=QXWjLA91"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=QXWjLA91" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/tCxnwSojqJY" height="1" width="1"/&gt;</content><link rel="related" href="http://code.google.com/p/trexma-for-firefox/" title="TrExMa for Firefox 0.6.2" /><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/45747871293083749/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=45747871293083749" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/45747871293083749?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/45747871293083749?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/tCxnwSojqJY/trexma-for-firefox-062.html" title="TrExMa for Firefox 0.6.2" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2008/10/trexma-for-firefox-062.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEIFRngzcCp7ImA9WxRUEkU.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-3882534481970686140</id><published>2008-10-08T13:31:00.004-04:00</published><updated>2008-11-21T10:48:37.688-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-21T10:48:37.688-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SAP Portal" /><category scheme="http://www.blogger.com/atom/ns#" term="SAP" /><category scheme="http://www.blogger.com/atom/ns#" term="CSS" /><title>SAP Portal Javascript/CSS Service Enhanced</title><content type="html">The &lt;a href="http://twofourone.blogspot.com/2008/10/hacking-sap-portal-with-javascriptcss.html"&gt;service&lt;/a&gt; was enhanced today.&lt;br /&gt;&lt;br /&gt;What I discovered whilst doing more testing is that the service inserted the content very early in the lifecycle of the Portal.&lt;br /&gt;&lt;br /&gt;So lets say you inserted your CSS which fixes a bunch of SAPisms you can't fix using the theme editor.  Using the service you'd get output that looked like this:&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt; &amp;lt;link href="/irj/portalapps/com.sap.portal.design.portaldesigndata/themes/portal/tillerTheme/glbl/glbl_nn7.css?7.0.15.0.25" rel="stylesheet"/&amp;gt;&lt;br /&gt; &amp;lt;link href="/irj/portalapps/com.sap.portal.design.portaldesigndata/themes/portal/tillerTheme/prtl_std/prtl_std_nn7.css?7.0.15.0.25" rel="stylesheet"/&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!-- EPCF: BOB Core --&amp;gt;&lt;br /&gt; &amp;lt;meta content="text/javascript" http-equiv="Content-Script-Type"/&amp;gt;&lt;br /&gt; &amp;lt;script src="/irj/portalapps/com.sap.portal.epcf.loader/script/optimize/js13_epcf.js?7.00001502"/&amp;gt;&lt;br /&gt; &amp;lt;script&amp;gt;&lt;br /&gt;  &amp;lt;!--&lt;br /&gt;// Snipped 30 lines of script&lt;br /&gt; &amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;!-- EPCF: EOB Core --&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;lt;!-- HTML Business for Java, 645_VAL_REL, 477869, Tue Feb 26 13:23:36 EST 2008 --&amp;gt;&lt;br /&gt; &amp;lt;!-- HTMLB: begin VARS --&amp;gt;&lt;br /&gt; &amp;lt;script language="JavaScript"&amp;gt;&lt;br /&gt;ur_system = {doc : window.document , mimepath :"/irj/portalapps/com.sap.portal.design.urdesigndata/themes/portal/tillerTheme/common/", stylepath : "/irj/portalapps/com.sap.portal.design.urdesigndata/themes/portal/tillerTheme/ur/", emptyhoverurl : "/irj/portalapps/com.sap.portal.htmlb/jslib/emptyhover.html", is508 : false, dateformat : 1, domainrelaxing : "MINIMAL"};&lt;br /&gt; &amp;lt;/script&amp;gt;&lt;br /&gt; &amp;lt;!-- HTMLB: end   VARS --&amp;gt;&lt;br /&gt; &amp;lt;link type="text/css" href="http://your/css.css" rel="stylesheet"/&amp;gt;&lt;br /&gt;a bunch of scripts&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Oops, you're not after the theme at all.  You'd have to use !important all over the place.  How does one resolve this?&lt;br /&gt;&lt;br /&gt;You enhance the service.  In this case I used an example found by decompiling the LAFService, which is the actual theme service.  It provided examples on how to implement and use new IResource and IResourceInformation objects.  Here's the ExternalResource IResource object:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;import com.sapportals.portal.prt.resource.IResource;&lt;br /&gt;import com.sapportals.portal.prt.resource.IResourceInformation;&lt;br /&gt;import java.io.Serializable;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* Describes a resource which resides outside of the Portal landscape&lt;br /&gt;* Such resources could be external Javascript toolkits or CSS pages&lt;br /&gt;*&lt;br /&gt;*/&lt;br /&gt;public class ExternalResource implements IResource, Serializable {&lt;br /&gt;private IResourceInformation mm_resInfo;&lt;br /&gt;&lt;br /&gt;public ExternalResource(){}&lt;br /&gt;&lt;br /&gt;public IResourceInformation getResourceInformation(){&lt;br /&gt; return mm_resInfo;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public void init(IResourceInformation resourceInformation) {&lt;br /&gt; mm_resInfo = resourceInformation;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public boolean isAvailable() {&lt;br /&gt; return mm_resInfo != null;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Doesn't do much does it.  All of the work is in the init method and using the ExternalResourceInformation object which is an IResourceInformation:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;import com.sapportals.portal.prt.component.IPortalComponentRequest;&lt;br /&gt;import com.sapportals.portal.prt.resource.IResourceInformation;&lt;br /&gt;import java.io.Serializable;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;*&lt;br /&gt;* Describes the ResourceInformation required by a resource that lives&lt;br /&gt;* outside of the Portal Landscape.&lt;br /&gt;*/&lt;br /&gt;public class ExternalResourceInformation  implements IResourceInformation, Serializable {&lt;br /&gt;&lt;br /&gt;private String mm_type;&lt;br /&gt;private String mm_fileName;&lt;br /&gt;private boolean mm_useFileName;&lt;br /&gt;private String mm_URL;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* @param resourceType - you should be using the static types defined in IResource.&lt;br /&gt;* @param URL - The URL to the resource you're trying to add&lt;br /&gt;*/&lt;br /&gt;public ExternalResourceInformation(String resourceType, String URL){&lt;br /&gt; mm_type = resourceType;&lt;br /&gt; mm_URL = URL;&lt;br /&gt;}&lt;br /&gt;/* (non-Javadoc)&lt;br /&gt;* @see com.sapportals.portal.prt.resource.IResourceInformation#getComponent()&lt;br /&gt;*/&lt;br /&gt;public String getComponent() {&lt;br /&gt; return "theNameOfYourService";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/* (non-Javadoc)&lt;br /&gt;* @see com.sapportals.portal.prt.resource.IResourceInformation#getType()&lt;br /&gt;*/&lt;br /&gt;public String getType() {&lt;br /&gt; return mm_type;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/* (non-Javadoc)&lt;br /&gt;* @see com.sapportals.portal.prt.resource.IResourceInformation#getSource()&lt;br /&gt;*/&lt;br /&gt;public String getSource() {&lt;br /&gt; return "";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/* (non-Javadoc)&lt;br /&gt;* @see com.sapportals.portal.prt.resource.IResourceInformation#getURL(com.sapportals.portal.prt.component.IPortalComponentRequest)&lt;br /&gt;*/&lt;br /&gt;public String getURL(IPortalComponentRequest arg0) {&lt;br /&gt; return getURL();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public String getURL() {&lt;br /&gt; return mm_URL;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;All of the work here is done in the constructor.  You just provide the URL and it will pass that to the IResource which will be included in the PortalResponse.&lt;br /&gt;&lt;br /&gt;What's amazing about this is how easy this actually was.  What's more amazing is how you need to decompile things to really understand how this works.  Most IResources are BaseResource objects.  Those objects are more complex since they need to ask the portal to build a URL to the resource you're attempting to include.  Therefore, using this method must be faster and lighter on the portal itself as well as the browser.&lt;br /&gt;&lt;br /&gt;One more thing to do.  Enhance the service objects:&lt;br /&gt;&lt;br /&gt;A new method signature in our interface:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;public IResource getExternalCssResource(String cssURL);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;New methods in our implementation:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;public IResource getExternalCssResource(String cssURL) {&lt;br /&gt; IResource ret = null;&lt;br /&gt; ret = getResource(IResource.CSS, cssURL);&lt;br /&gt; return ret;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private IResource getResource(String URL) {&lt;br /&gt;  IResource er = new ExternalResource();&lt;br /&gt;  IResourceInformation ri = new ExternalResourceInformation(resourceType, URL);&lt;br /&gt;  er.init(ri);&lt;br /&gt;  return er;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's it.&lt;br /&gt;&lt;br /&gt;Now, if you create a simple "footer" portal component which does nothing but insert your IResource, you can have your CSS at the bottom of your page.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;IPortalComponentResponse componentResponse = (IPortalComponentResponse)pageContext.getAttribute(javax.servlet.jsp.PageContext.RESPONSE);&lt;br /&gt;IHtmlHeadService htmlHeadService =&lt;br /&gt; (IHtmlHeadService) PortalRuntime.getRuntimeResources().getService("com.scotts.tiller.portal.layouts.htmlheadservice.HtmlHeadService");&lt;br /&gt;IResource res = htmlHeadService.getExternalScriptResource("http://your/css.css");&lt;br /&gt;componentResponse.include(componentRequest, res);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now your stylesheet will appear after your portal theme.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-3882534481970686140?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/642rM-kBeRitXE3jHU_bkebwQNQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/642rM-kBeRitXE3jHU_bkebwQNQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/642rM-kBeRitXE3jHU_bkebwQNQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/642rM-kBeRitXE3jHU_bkebwQNQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=AZ8UfAoT"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=XjZsnqUv"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=pXXpA7Gz"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=pXXpA7Gz" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=6DhJxeqS"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=6DhJxeqS" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=WALAJbof"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=WALAJbof" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/sX0Zny8gnhE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/3882534481970686140/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=3882534481970686140" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/3882534481970686140?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/3882534481970686140?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/sX0Zny8gnhE/sap-portal-javascriptcss-service.html" title="SAP Portal Javascript/CSS Service Enhanced" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2008/10/sap-portal-javascriptcss-service.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEQDSH44cCp7ImA9WxRUEkU.&quot;"><id>tag:blogger.com,1999:blog-6958306.post-173287070754717233</id><published>2008-10-06T14:21:00.005-04:00</published><updated>2008-11-21T10:46:19.038-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-21T10:46:19.038-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SAP Portal" /><category scheme="http://www.blogger.com/atom/ns#" term="Javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="SAP" /><category scheme="http://www.blogger.com/atom/ns#" term="CSS" /><title>Hacking SAP Portal with a Javascript/CSS Service</title><content type="html">One of the more, erm, "interesting features" of SAP Portal is the lack of ability to directly access the HTML HEAD tag and insert SCRIPT and LINK tags to your own CSS and Javascripts.  Well it's not impossible to do, but SAP doesn't offer this out of the box straight away.  Probably because they don't want you breaking things.&lt;br /&gt;&lt;br /&gt;But lets say you want to use the Dojo Toolkit or YUI on a new hip AbstractPortalComponent.  But you don't want to download and host the scripts locally.  You wish to use AOLs CDN or Yahoo's CDN to load the javascript.  It's faster and solid in terms of reliability. How can you accomplish this?&lt;br /&gt;&lt;br /&gt;The answer is, you need to write a new service to access the HTML HEAD.&lt;br /&gt;&lt;br /&gt;Create a service inside NWDS and call it, HtmlHeadService.  NWDS will create an interface for the service and an implementation.  Go to IHtmlHeadService and insert the following method signatures:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;package com.portal.htmlheadservice;&lt;br /&gt;&lt;br /&gt;import com.sapportals.portal.prt.service.IService;&lt;br /&gt;import com.sapportals.portal.prt.component.IPortalComponentRequest;&lt;br /&gt;&lt;br /&gt;public interface IHtmlHeadService extends IService {&lt;br /&gt;&lt;br /&gt;  public static final String KEY = "HtmlHeadService";&lt;br /&gt;&lt;br /&gt;  public void addScript(IPortalComponentRequest request, String scriptURL, String type);&lt;br /&gt;  public void addJS(IPortalComponentRequest request, String jsURL);&lt;br /&gt;  public void addLink(IPortalComponentRequest request, String linkURL, String type, String rel);&lt;br /&gt;  public void addCSSLink(IPortalComponentRequest request, String linkURL);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is pretty simple so far.   Next look at the HtmlHeadService object:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;package com.portal.htmlheadservice;&lt;br /&gt;&lt;br /&gt;import com.sapportals.portal.prt.service.IServiceContext;&lt;br /&gt;import com.sapportals.portal.prt.logger.ILogger;&lt;br /&gt;import com.sapportals.portal.prt.runtime.IPortalConstants;&lt;br /&gt;import com.sapportals.portal.prt.component.IPortalComponentRequest;&lt;br /&gt;import com.sapportals.portal.prt.pom.IPortalNode;&lt;br /&gt;import com.sapportals.portal.prt.connection.PortalHtmlResponse;&lt;br /&gt;import com.sapportals.portal.prt.connection.IPortalResponse;&lt;br /&gt;import com.sapportals.portal.prt.util.html.HtmlDocument;&lt;br /&gt;import com.sapportals.portal.prt.util.html.HtmlHead;&lt;br /&gt;import com.sapportals.portal.prt.util.html.HtmlScript;&lt;br /&gt;import com.sapportals.portal.prt.util.html.HtmlLink;&lt;br /&gt;&lt;br /&gt;public class HtmlHeadService implements IHtmlHeadService{&lt;br /&gt;&lt;br /&gt;  private IServiceContext mm_serviceContext;&lt;br /&gt;  private ILogger         mm_logger;&lt;br /&gt;&lt;br /&gt;  public void init(IServiceContext serviceContext) {&lt;br /&gt;      mm_serviceContext = serviceContext;&lt;br /&gt;      mm_logger = serviceContext.getLogger(IPortalConstants.SERVICE_LOGGER);&lt;br /&gt;      mm_logger.info(this, "Initialization of HtmlHeadAccessor");&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void afterInit() {&lt;br /&gt;      mm_logger.info(this, "After Initialization of HtmlHeadAccessor");&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void configure(com.sapportals.portal.prt.service.IServiceConfiguration configuration) {}&lt;br /&gt;&lt;br /&gt;  public void destroy() {}&lt;br /&gt;&lt;br /&gt;  public void release()  {}&lt;br /&gt;&lt;br /&gt;  public IServiceContext getContext() {&lt;br /&gt;      return mm_serviceContext;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public String getKey() {&lt;br /&gt;      return KEY;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void addLink(IPortalComponentRequest request, String linkURL, String type, String rel) {&lt;br /&gt;      HtmlHead docHead = getHtmlHead(request);&lt;br /&gt;      if (docHead != null) {&lt;br /&gt;          HtmlLink link = new HtmlLink(linkURL);&lt;br /&gt;          link.setType(type);&lt;br /&gt;          link.setRel(rel);&lt;br /&gt;          docHead.addElement(link);&lt;br /&gt;      } else {&lt;br /&gt;          mm_logger.severe("Could not get HtmlHead from PortalResponse");&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void addCSSLink(IPortalComponentRequest request, String linkURL) {&lt;br /&gt;      addLink(request, linkURL, "text/css", "stylesheet");&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void addScript(IPortalComponentRequest request, String scriptURL, String type) {&lt;br /&gt;      HtmlHead docHead = getHtmlHead(request);&lt;br /&gt;      if (docHead != null) {&lt;br /&gt;          HtmlScript script = new HtmlScript();&lt;br /&gt;          script.setSrc(scriptURL);&lt;br /&gt;          script.setType(type);&lt;br /&gt;          docHead.addElement(script);&lt;br /&gt;      } else {&lt;br /&gt;          mm_logger.severe("Could not get HtmlHead from PortalResponse");&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  public void addJS(IPortalComponentRequest request, String jsURL) {&lt;br /&gt;      addScript(request, jsURL, "text/javascript");&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /* This contains the deprecated method getHtmlDocument().  If this fails, check&lt;br /&gt;   * the Web Page Composer based service cssService.  It uses the exact same&lt;br /&gt;   * method.  If this is failing, it should be failing.&lt;br /&gt;   */&lt;br /&gt;  private HtmlHead getHtmlHead(IPortalComponentRequest request) {&lt;br /&gt;      HtmlHead docHead = null;&lt;br /&gt;      IPortalNode node = request.getNode().getPortalNode();&lt;br /&gt;      IPortalResponse resp = (IPortalResponse) node.getValue(IPortalResponse.class.getName());&lt;br /&gt;      try {&lt;br /&gt;          PortalHtmlResponse htmlResp = (PortalHtmlResponse) resp;&lt;br /&gt;          HtmlDocument doc = htmlResp.getHtmlDocument();&lt;br /&gt;          docHead = doc.getHead();&lt;br /&gt;      } catch (Exception cce) {&lt;br /&gt;          mm_logger.severe("Exception found: " + cce.getMessage());&lt;br /&gt;          cce.printStackTrace(System.err);&lt;br /&gt;      }&lt;br /&gt;      return docHead;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's the meat of the matter.  What does this code do?  It uses some undocumented objects to gain access to an HtmlDocument object.  This object gives you full access to the entire web page.  In this case we're just grabbing a head, you could do much more if you so choose.&lt;br /&gt;&lt;br /&gt;So what about the deprecated method getHtmlDocument(), seems bad.  Well, with the exception of the fact that SAP is using the exact same method in the recently released Web Page Composer tool, I wouldn't be worried.  WPC uses this method to grab its style sheets and javascripts from the KM repositiory.  The cool thing is, the code can be repurposed to place anything you like into the page.&lt;br /&gt;&lt;br /&gt;How to finalize the service?  It needs a ton of SharingReferences in the portalapp.xml file to make it go.  This is probably more than it needs, but cssService was using this exact string:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;"connection,usermanagement, knowledgemanagement, landscape, htmlb, exportalJCOclient, exportal"&lt;/pre&gt;&lt;br /&gt;With this service you can easily create a PortalComponent that accesses external stylesheets and javascripts to give your portal that custom look and feel that it's been lacking.  Some folks have used this method to change the Portal Title and other features as well.  Thanks to &lt;a href="https://www.sdn.sap.com/irj/sdn/profile?userID=965&amp;amp;start=0"&gt;Darrell Merryweather&lt;/a&gt; at SAP for the inspiration.&lt;div class="blogger-post-footer"&gt;&lt;script type="text/javascript"&gt;&lt;!--
google_ad_client = "pub-7737529800615990";
/* RSS Ad, created 2/26/08 */
google_ad_slot = "0334624276";
google_ad_width = 468;
google_ad_height = 15;
//--&gt;
&lt;/script&gt;
&lt;script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"&gt;
&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6958306-173287070754717233?l=twofourone.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/-0Df5VTv6zC9Ok0z45PUzrZVc04/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/-0Df5VTv6zC9Ok0z45PUzrZVc04/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/-0Df5VTv6zC9Ok0z45PUzrZVc04/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/-0Df5VTv6zC9Ok0z45PUzrZVc04/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=iph4OWVl"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=41" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=VKhtpexU"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?d=52" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=NJnsvQqY"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=NJnsvQqY" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=z5I8FKeV"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=z5I8FKeV" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~f/TwoFourOne?a=FoZUGqUQ"&gt;&lt;img src="http://feeds.feedburner.com/~f/TwoFourOne?i=FoZUGqUQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/TwoFourOne/~4/OubGFNJhnAc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://twofourone.blogspot.com/feeds/173287070754717233/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=6958306&amp;postID=173287070754717233" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/173287070754717233?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6958306/posts/default/173287070754717233?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/TwoFourOne/~3/OubGFNJhnAc/hacking-sap-portal-with-javascriptcss.html" title="Hacking SAP Portal with a Javascript/CSS Service" /><author><name>Mike</name><uri>http://www.blogger.com/profile/04074103559645856043</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>2</thr:total><feedburner:origLink>http://twofourone.blogspot.com/2008/10/hacking-sap-portal-with-javascriptcss.html</feedburner:origLink></entry></feed>

