<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>hross.net</title>
 <link href="http://feeds.feedburner.com/hross" rel="self"/>
 <link href="http://blog.hross.net/"/>
 <updated>2017-05-30T15:19:25+00:00</updated>
 <id>http://blog.hross.net/</id>
 <author>
   <name>hross</name>
   <email>hross@hross.net</email>
 </author>

 
 <entry>
   <title>Headless Chrome Basic Auth</title>
   <link href="http://blog.hross.net/javascript/2017/05/30/headless-chrome.html"/>
   <updated>2017-05-30T00:00:00+00:00</updated>
   <id>http://blog.hross.net/javascript/2017/05/30/headless-chrome</id>
   <content type="html">&lt;p&gt;I recently spent some time checking out &lt;a href=&quot;https://developers.google.com/web/updates/2017/04/headless-chrome&quot;&gt;Headless Chrome&lt;/a&gt; since it seems it will probably at least kill &lt;a href=&quot;http://phantomjs.org/&quot;&gt;PhantomJS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It doesn’t yet fully appear ready for prime time but, &lt;a href=&quot;https://medium.com/@dschnr/using-headless-chrome-as-an-automated-screenshot-tool-4b07dffba79a&quot;&gt;there are some cool uses&lt;/a&gt; already in the wild.&lt;/p&gt;

&lt;p&gt;All I wanted to do was hit a page that had basic auth enabled, but it turned out to be more of a task than I thought. Here are some of my notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Get Ubuntu box up and running.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Install latest Chrome:&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(From &lt;a href=&quot;https://askubuntu.com/questions/79280/how-to-install-chrome-browser-properly-via-command-line&quot;&gt;askubuntu&lt;/a&gt;)&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt-get install libxss1 libappindicator1 libindicator7
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome*.deb
sudo apt-get install -f
sudo dpkg -i google-chrome*.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;Test Headless Chrome:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;google-chrome-stable --headless --disable-gpu --no-sandbox --remote-debugging-port=9222 --remote-debugging-address=10.138.33.145  http://www.google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Navigating to that port/IP should display a page you can debug your Chrome session on.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Install node, npm, chrome-remote-interface.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Set up &lt;a href=&quot;https://www.npmjs.com/package/chrome-remote-interface&quot;&gt;chrome-remote-inteface&lt;/a&gt; node script.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Use &lt;a href=&quot;https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Headers&quot;&gt;Chrome DevTools docs&lt;/a&gt; to browse options.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Come up with something like &lt;a href=&quot;https://github.com/hross/headless-chrome-basic-auth&quot;&gt;this repo here&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Git Dates</title>
   <link href="http://blog.hross.net/git/microsoft/2016/03/30/git-dates.html"/>
   <updated>2016-03-30T00:00:00+00:00</updated>
   <id>http://blog.hross.net/git/microsoft/2016/03/30/git-dates</id>
   <content type="html">&lt;p&gt;Here is another article I recently released via Microsoft.
Pretty simple, but handy for keeping track of &lt;a href=&quot;https://www.visualstudio.com/en-us/articles/git-dates&quot;&gt;Git Dates&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Git History Simplification</title>
   <link href="http://blog.hross.net/git/microsoft/2016/02/23/git-history-simplification.html"/>
   <updated>2016-02-23T00:00:00+00:00</updated>
   <id>http://blog.hross.net/git/microsoft/2016/02/23/git-history-simplification</id>
   <content type="html">&lt;p&gt;I have been pretty busy working on our Git implementation in &lt;a href=&quot;https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx&quot;&gt;Visual Studio Team Services&lt;/a&gt; here at Microsoft, so I have not been posting much. 
However, I did find some time to write an article about &lt;a href=&quot;https://www.visualstudio.com/en-us/articles/git-log-history-simplification&quot;&gt;Git History Simplification&lt;/a&gt; over on the Visual Studio site.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How to Evaluate Product Management at a Technology Company</title>
   <link href="http://blog.hross.net/technology/management/2016/02/12/howtoevaluateproduct.html"/>
   <updated>2016-02-12T00:00:00+00:00</updated>
   <id>http://blog.hross.net/technology/management/2016/02/12/howtoevaluateproduct</id>
   <content type="html">&lt;p&gt;Part 3 of 3, been up for a while at Medium: &lt;a href=&quot;https://medium.com/@hross/how-to-evaluate-product-management-at-a-technology-company-57fd8cf698c1&quot;&gt;How to Evaluate Product Management at a Technology Company&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How to Evaluate Engineering at a Technology Company</title>
   <link href="http://blog.hross.net/technology/management/2015/09/18/howtoevaluateengineering.html"/>
   <updated>2015-09-18T00:00:00+00:00</updated>
   <id>http://blog.hross.net/technology/management/2015/09/18/howtoevaluateengineering</id>
   <content type="html">&lt;p&gt;Part 2 of 3, now up over at Medium: &lt;a href=&quot;https://medium.com/@hross/how-to-evaluate-engineering-at-a-technology-company-8cc79681701&quot;&gt;How to Evaluate Engineering at a Technology Company&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How to Evaluate Management at a Technology Company</title>
   <link href="http://blog.hross.net/technology/management/2015/09/03/howtoevaluatemanagement.html"/>
   <updated>2015-09-03T00:00:00+00:00</updated>
   <id>http://blog.hross.net/technology/management/2015/09/03/howtoevaluatemanagement</id>
   <content type="html">&lt;p&gt;Usually I post more tech stuff on this blog (when I actually post), so I thought I’d start a blog over at &lt;a href=&quot;https://medium.com/&quot;&gt;Medium&lt;/a&gt; that is a little more squishy. Check out my first post on &lt;a href=&quot;https://medium.com/@hross/how-to-evaluate-management-at-a-technology-company-f0d30d6fe72c&quot;&gt;How to Evaluate Management at a Technology Company&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Reading SEC Data From EDGAR in C#</title>
   <link href="http://blog.hross.net/c%23/2013/04/13/csharpedgar.html"/>
   <updated>2013-04-13T00:00:00+00:00</updated>
   <id>http://blog.hross.net/c%23/2013/04/13/csharpedgar</id>
   <content type="html">&lt;p&gt;Since I’ve been working on side projects, moving to North Carolina, and generally having a life, I have not updated this blog much. That doesn’t mean
I haven’t been working on things, though. One of my more recent projects has been scraping the SEC EDGAR database for
public filing data on companies. Surprisingly, I found very little in the C# realm for doing this.&lt;/p&gt;

&lt;p&gt;I wrote a bunch of stuff to help me grab the data and shove it into a database using &lt;a href=&quot;https://servicestack.net/&quot;&gt;ServiceStack&lt;/a&gt;. Nothing earth shattering, but if you are interested in learning how to
evaluate companies, or you just want this kind of data, it’s a great place to start.&lt;/p&gt;

&lt;p&gt;Take a look at the &lt;a href=&quot;https://github.com/hross/stock-info-downloader&quot;&gt;Github Project&lt;/a&gt; for more info.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Charity Campaign (building a node.js web app)</title>
   <link href="http://blog.hross.net/node.js/mongodb/2011/09/09/charitycampaign.html"/>
   <updated>2011-09-09T00:00:00+00:00</updated>
   <id>http://blog.hross.net/node.js/mongodb/2011/09/09/charitycampaign</id>
   <content type="html">&lt;p&gt;Over the past month or so I’ve been putting together a &lt;a href=&quot;http://nodejs.org/&quot;&gt;node.js&lt;/a&gt; application for fun. The goal was to get some “real world” development experience with &lt;a href=&quot;http://nodejs.org/&quot;&gt;node.js&lt;/a&gt; and &lt;a href=&quot;http://www.mongodb.org/&quot;&gt;mongodb&lt;/a&gt;, figure out what the challenges are (is event oriented programming hard? how will I live life without a traditional RDBMS?) and build something people might actually use.&lt;/p&gt;

&lt;p&gt;To wit: &lt;a href=&quot;https://github.com/hross/Charity-Campaign&quot;&gt;Charity Campaign&lt;/a&gt;… an application for tracking charity drive donations. The basic concept is to have teams of users submitting various items, assign bonuses to those users based on what they submit, give some basic security/management and display stats.&lt;/p&gt;

&lt;p&gt;The idea isn’t new, &lt;a href=&quot;http://techopener.com/&quot;&gt;other people I know have written Google App Engine applications for it&lt;/a&gt;, but its a lot easier to focus on a niche you know than make up a new one for the purposes of writing software.&lt;/p&gt;

&lt;p&gt;Without further ado, here are some of the pieces/concepts I used to build the application:&lt;/p&gt;

&lt;h3 id=&quot;nodejs-packages&quot;&gt;node.js packages&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://expressjs.com/&quot;&gt;express&lt;/a&gt; for most of the http framework and middleware support&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/visionmedia/connect-form&quot;&gt;connect-form&lt;/a&gt; for file uploads&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://ldapjs.org/&quot;&gt;ldapjs&lt;/a&gt; for ldap connectivity&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/christkv/node-mongodb-native&quot;&gt;mongodb&lt;/a&gt; for mongo db connectivity&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/caolan/async&quot;&gt;async&lt;/a&gt; for keeping my sanity when writing event driven code that had dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;conceptsinfo&quot;&gt;concepts/info&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/visionmedia/express/tree/master/examples&quot;&gt;vision media samples&lt;/a&gt; were uber helpful in learning how to use express. &lt;a href=&quot;https://github.com/visionmedia/express/tree/master/examples/mvc&quot;&gt;The mvc sample&lt;/a&gt; is the basis for my routing framework.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://stackoverflow.com/&quot;&gt;Stackoverflow&lt;/a&gt; was useful for finding &lt;a href=&quot;http://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files&quot;&gt;configuration management information&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Found a good &lt;a href=&quot;http://blog.james-carr.org/2010/07/07/parsing-csv-files-with-nodejs/&quot;&gt;csv parsing&lt;/a&gt; starting point (ended up hacking this code to bits, but the basics and the regex are there).&lt;/p&gt;

&lt;p&gt;Twitter recently released &lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;boostrap&lt;/a&gt; and it seemed cool so I converted the user interface to use it (previously I was using some free html template or other).&lt;/p&gt;

&lt;h3 id=&quot;false-starts&quot;&gt;false starts&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://geddyjs.org/&quot;&gt;Geddy&lt;/a&gt; looked like a good MVC starting place, but development appears to be halted so I didn’t get far.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://blog.learnboost.com/blog/mongoose/&quot;&gt;Mongoose&lt;/a&gt; looked cool, but seemed a bit heavy and constrictive. It probably would have helped with data validation and error handling, though.&lt;/p&gt;

&lt;p&gt;I initially started with &lt;a href=&quot;http://couchdb.apache.org/&quot;&gt;couch-db&lt;/a&gt; and &lt;a href=&quot;https://github.com/cloudhead/cradle&quot;&gt;cradle&lt;/a&gt;, but I didn’t care about the replication or json consumption features and &lt;a href=&quot;http://stackoverflow.com/questions/5073343/approaches-to-generate-auto-incrementing-numeric-ids-in-couchdb&quot;&gt;generating unique integers for slugs started to become a PIA&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://www.mongodb.org/display/DOCS/Tutorial&quot;&gt;mongodb tutorials&lt;/a&gt; were much more accessible and &lt;a href=&quot;http://www.mongodb.org/display/DOCS/Object+IDs&quot;&gt;accommodating&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;summary&quot;&gt;summary&lt;/h3&gt;

&lt;p&gt;Hopefully this application actually gets used at some point. Even if it doesn’t, building and committing it to github has been a satisfying experience in and of itself.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Converting HTML to Markdown</title>
   <link href="http://blog.hross.net/markdown/2011/09/07/markdown.html"/>
   <updated>2011-09-07T00:00:00+00:00</updated>
   <id>http://blog.hross.net/markdown/2011/09/07/markdown</id>
   <content type="html">&lt;p&gt;I started converting some old posts and found it was a breeze (other than the annoyance of link conversion). There is a great web based HTML to markdown converter. Here are a couple of links in case you’re in the same situation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://heckyesmarkdown.com/&quot;&gt;HTML to Markdown converter&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://warpedvisions.org/projects/markdown-cheat-sheet/&quot;&gt;A quick markdown cheat sheet&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;http://daringfireball.net/projects/markdown/syntax&quot;&gt;A sweet markdown primer&lt;/a&gt;&lt;/p&gt;

  &lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Setting up a Drupal Training Instance</title>
   <link href="http://blog.hross.net/drupal/2011/09/07/drupaltraining.html"/>
   <updated>2011-09-07T00:00:00+00:00</updated>
   <id>http://blog.hross.net/drupal/2011/09/07/drupaltraining</id>
   <content type="html">&lt;p&gt;In a couple of days I’m providing Drupal training at &lt;a href=&quot;http://ppc.com/&quot;&gt;PPC&lt;/a&gt;. The goal of the training is to provide an overview and “get your feet wet” with Drupal.&lt;/p&gt;

&lt;p&gt;I found a couple of cool things while looking for some decent training resources. First, the &lt;a href=&quot;http://drupal.org/handbooks&quot;&gt;handbooks on drupal.org&lt;/a&gt; are top notch (and free). Second, there is an excellent (free) ebook over at &lt;a href=&quot;http://learnbythedrop.com/&quot;&gt;learnbythedrop&lt;/a&gt; called &lt;a href=&quot;http://learnbythedrop.com/buildingyourblog&quot;&gt;Building Your Blog With Drupal&lt;/a&gt;. It covers a soup to nuts install and configuration of a blogging platform (and is pretty up to date as of this writing). Perfect for a beginning training session.&lt;/p&gt;

&lt;p&gt;To facilitate the training I set up an &lt;a href=&quot;http://nginx.net/&quot;&gt;nginx&lt;/a&gt; install with &lt;a href=&quot;http://www.php.net/&quot;&gt;php&lt;/a&gt; on my local windows machine (the training is in windows to make things less of a culture shock). You can find a great tutorial on doing that &lt;a href=&quot;http://eksith.wordpress.com/2008/12/08/nginx-php-on-windows/&quot;&gt;here&lt;/a&gt; and an nginx config tailored for Drupal &lt;a href=&quot;http://wiki.nginx.org/Drupal&quot;&gt;here&lt;/a&gt;. Finally, I set up &lt;a href=&quot;http://www.mysql.com/&quot;&gt;mysql&lt;/a&gt; with &lt;a href=&quot;http://www.phpmyadmin.net/home_page/index.php&quot;&gt;phpmyadmin&lt;/a&gt; and I was ready to rock.&lt;/p&gt;

&lt;p&gt;The best part of this is that I needed to set up 25+ training machines with the same configuration. Because it’s all batch file oriented, I simply had to copy the entire directory tree, &lt;a href=&quot;http://www.clockwatchers.com/mysql_dump.html&quot;&gt;take a dump of the database&lt;/a&gt; and copy the whole thing to an &lt;a href=&quot;http://aws.amazon.com/ec2/&quot;&gt;EC2&lt;/a&gt; windows instance. Finally, with a little &lt;a href=&quot;https://iain.cx/src/nssm/&quot;&gt;nssm&lt;/a&gt; goodness I was able to make the whole thing automatic.&lt;/p&gt;

&lt;p&gt;Presto. Drupal training.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Adventures in GitHub Blogging</title>
   <link href="http://blog.hross.net/github/2011/09/05/gitblog.html"/>
   <updated>2011-09-05T00:00:00+00:00</updated>
   <id>http://blog.hross.net/github/2011/09/05/gitblog</id>
   <content type="html">&lt;p&gt;I got tired of &lt;a href=&quot;http://www.drupal.org&quot;&gt;Drupal&lt;/a&gt; as a blogging platform awfully quickly. It ended up being too much for my needs (as is probably the case with anyone who is using it solely for blogging).&lt;/p&gt;

&lt;p&gt;I stumbled across a couple of interesting solutions (&lt;a href=&quot;http://www.allthingsdistributed.com/2011/02/website_amazon_s3.html&quot;&gt;like hosting on s3&lt;/a&gt;). Ultimately, though, I like the idea of keeping it simple and having the content in a format I can easily export &lt;em&gt;and read with a text editor&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://explore.live.com/windows-live-writer&quot;&gt;Windows Live Writer&lt;/a&gt; is still the best blogging tool I’ve ever used… too bad I’ve started focusing some of my development efforts on my mac lately. Time to move back to a text editor…&lt;/p&gt;

&lt;p&gt;Luckily, &lt;a href=&quot;http://github.com&quot;&gt;github&lt;/a&gt; has some awesome abilities to both version your blog and host it at the same time, thanks to &lt;a href=&quot;http://pages.github.com/&quot;&gt;github pages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tom Preston Werner has an &lt;a href=&quot;http://tom.preston-werner.com/2008/11/17/blogging-like-a-hacker.html&quot;&gt;interesting post about doing just that&lt;/a&gt;, as do &lt;a href=&quot;http://alexyoung.org/2009/07/09/new-blog/&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;http://ostatic.com/blog/build-your-site-with-jekyll&quot;&gt;other&lt;/a&gt; &lt;a href=&quot;https://github.com/jamesyu/jamesyu_jekyll_template&quot;&gt;people&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cool stuff:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I can use &lt;a href=&quot;http://warpedvisions.org/projects/markdown-cheat-sheet/&quot;&gt;markdown&lt;/a&gt; for my blog posts&lt;/li&gt;
  &lt;li&gt;My entire blog is &lt;a href=&quot;https://github.com/hross/hross.github.com&quot;&gt;automagically in source control&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Updating entries is as simple as making changes using git&lt;/li&gt;
  &lt;li&gt;I can edit the blog entries on &lt;a href=&quot;http://github.com&quot;&gt;github&lt;/a&gt; from anywhere I have an internet connection&lt;/li&gt;
  &lt;li&gt;Pretty much free, minus the cost of pointing a domain name at the blog if I so choose.&lt;/li&gt;
  &lt;li&gt;Preston-Warner makes his &lt;a href=&quot;https://github.com/mojombo/tpw&quot;&gt;blog template freely available on github&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://disqus.com&quot;&gt;diqus&lt;/a&gt; works great for comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not as cool stuff:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;No fancy editors (I am more than okay with that)&lt;/li&gt;
  &lt;li&gt;I have to maintain my own HTML templates, SEO, etc&lt;/li&gt;
  &lt;li&gt;I have to use git (pro and con)&lt;/li&gt;
  &lt;li&gt;Have to learn yet another templating language (Jekyll). This looks useful: &lt;a href=&quot;https://github.com/mojombo/jekyll/wiki/template-data&quot;&gt;Jekyll Template Wiki&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;All my blog updates are public on github (unless I want to start paying them for private repos)&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Debugging Drupal Cron</title>
   <link href="http://blog.hross.net/drupal/2011/06/01/debugdrupalcron.html"/>
   <updated>2011-06-01T00:00:00+00:00</updated>
   <id>http://blog.hross.net/drupal/2011/06/01/debugdrupalcron</id>
   <content type="html">&lt;p&gt;Recently, we had some issues with Drupal cron and indexing of Solr results, so I figured I’d share a couple of quick tips on debugging Drupal cron:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Get &lt;a href=&quot;http://drupal.org/project/supercron&quot;&gt;Supercron&lt;/a&gt;. That will let you individually run each cron task so you can figure out which one is failing.&lt;/li&gt;
  &lt;li&gt;Usually the problem is with search_cron. In order to dig deeper into this, you can hack core with watchdog statements and run a few database queries to identify problematic nodes. &lt;a href=&quot;http://drupal.org/node/361171&quot;&gt;See this thread here for a quick how to&lt;/a&gt;. Here is an example of a hacked up search_cron function for debugging purposes:&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function search_cron() {
	// We register a shutdown function to ensure that search_total is always up
	// to date.
	register_shutdown_function('search_update_totals');
	
	// Update word index
	foreach (module_list() as $module) {
		watchdog('search_debug', 'update index for ' . $module);
		module_invoke($module, 'update_index');
	}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Finally, if things get really hairy, you can build your search cron task into a module and run it by itself on demand. Build your own search cron task and exclude it. &lt;a href=&quot;http:/drupal.org/node/635480&quot;&gt;Details here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Quick and Dirty Drupal Profiling</title>
   <link href="http://blog.hross.net/drupal/2011/04/26/drupalprofiling.html"/>
   <updated>2011-04-26T00:00:00+00:00</updated>
   <id>http://blog.hross.net/drupal/2011/04/26/drupalprofiling</id>
   <content type="html">&lt;p&gt;One major question I had when I first started debugging module code was “how can I see how my code is performing?”. Turns out its pretty easy to get this info in a variety of ways.&lt;/p&gt;

&lt;h3 id=&quot;xhprof&quot;&gt;XHProf&lt;/h3&gt;

&lt;p&gt;The talk of the PHP profiling town seems to be &lt;a href=&quot;https://github.com/facebook/xhprof&quot;&gt;Facebook’s latest entry XHProf&lt;/a&gt;. For what I’m looking for it’s a bit heavy, and it only seems to work on Linux, which is a problem, since I’m developing and deploying on a mac and a wintel box.&lt;/p&gt;

&lt;h3 id=&quot;xdebug&quot;&gt;Xdebug&lt;/h3&gt;

&lt;p&gt;Ah yes, my old friend &lt;a href=&quot;http://www.xdebug.org&quot;&gt;XDebug&lt;/a&gt;. As it turns out, you can set up function profiling in XDebug with a couple of php.ini options. I won’t spend too much time on setup, since Zend did an exhaustive job of explaining it &lt;a href=&quot;http://devzone.zend.com/article/2899&quot;&gt;here&lt;/a&gt;. Or you can probably just take a look at the great &lt;a href=&quot;http://www.xdebug.org/docs/profiler&quot;&gt;documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;webgrind&quot;&gt;Webgrind&lt;/h3&gt;

&lt;p&gt;Okay, so you created yourself a bunch of cachegrind files and you’re ready to see what’s happening. You now have a couple of options, depending on your platform (&lt;a href=&quot;http://sourceforge.net/projects/wincachegrind/&quot;&gt;WinCacheGrind&lt;/a&gt; on Windows, KCacheGrind on &lt;a href=&quot;http://kcachegrind.sourceforge.net/html/Home.html&quot;&gt;Linux&lt;/a&gt; or &lt;a href=&quot;http://www.maccallgrind.com/&quot;&gt;MacCallGrind&lt;/a&gt; on a Mac). However… the Mac version isn’t free and setting up Linux compatibility stuff to view a call stack isn’t my thing. Turns out there is a truly righteous web based grind file viewer called &lt;a href=&quot;http://code.google.com/p/webgrind/&quot;&gt;webgrind&lt;/a&gt;. Cross platform and super easy install. Sweet. Only problem you may have is that large cachegrid files will take forever to load.&lt;/p&gt;

&lt;h3 id=&quot;devel-module&quot;&gt;Devel Module&lt;/h3&gt;

&lt;p&gt;So what about Drupal specific profiling information? Turns out there is a great module for that called &lt;a href=&quot;http://drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;. It will give you SQL statement profiling and memory usage, and a variety of other features.&lt;/p&gt;

&lt;p&gt;Finally, on the OS side you have your system specific tools like Process Explorer, Activity Monitor, top, etc.. Happy Profiling!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Quick and Dirty Drupal Debugging</title>
   <link href="http://blog.hross.net/drupal/2011/04/14/drupaldebugging.html"/>
   <updated>2011-04-14T00:00:00+00:00</updated>
   <id>http://blog.hross.net/drupal/2011/04/14/drupaldebugging</id>
   <content type="html">&lt;p&gt;Over the past few months I’ve had the opportunity to branch out and  hack on some Drupal modules. In doing so, I noticed that (a) I had no  idea what I was doing, and (b) there wasn’t a simple, easy description  of how to debug Drupal code. Hopefully this will serve as a quick and dirty primer.&lt;/p&gt;

&lt;h3 id=&quot;watchdog&quot;&gt;Watchdog&lt;/h3&gt;

&lt;p&gt;This is the quick and dirty way to “write to console” in Drupal. Simply putting the following PHP statement in a module:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;watchdog(&quot;foobar&quot;, &quot;hello world&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Will print to the Drupal log (Admin&lt;/td&gt;
      &lt;td&gt;Recent Log Messages).&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;xdebug&quot;&gt;XDebug&lt;/h3&gt;

&lt;p&gt;I highly recommend you immediately download and install &lt;a href=&quot;http://www.xdebug.org/&quot;&gt;XDebug&lt;/a&gt; for your PHP installation. Many *AMP stacks include it by default (&lt;a href=&quot;http://www.mamp.info/en/index.html&quot;&gt;MAMP&lt;/a&gt; and &lt;a href=&quot;http://www.apachefriends.org/en/xampp.html&quot;&gt;XAMPP&lt;/a&gt; among them).&lt;/p&gt;

&lt;p&gt;Along with a bunch of other stuff, it will “pretty print”  PHP error messages in HTML, so the next time you take your Drupal  instance down by forgetting a semicolon, you’ll see a nicely formatted call stack, memory profile, etc.. Installing it is ridiculously easy,  since &lt;a href=&quot;http://www.xdebug.org/find-binary.php&quot;&gt;they give you very specific instructions based on your configuration&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;var_dump-and-debug_backtrace&quot;&gt;var_dump and debug_backtrace&lt;/h3&gt;

&lt;p&gt;Here is where XDebug comes in really handy, since it auto-formats these functions when they are used. In a nutshell, &lt;a href=&quot;http://php.net/manual/en/function.var-dump.php&quot;&gt;var_dump&lt;/a&gt; will literally dump the contents of any variable directly to the current HTML page it executes on. If you add it to some module code, it  will crap its output directly to the screen.&lt;/p&gt;

&lt;p&gt;Even more useful is the &lt;a href=&quot;http://php.net/manual/en/function.debug-backtrace.php&quot;&gt;debug_backtrace&lt;/a&gt; function, which gives you an array that contains the entire stack trace at the time it was called, &lt;em&gt;and&lt;/em&gt; the values of the variables that were passed to all of the functions in  that stack trace (you need XDebug to really appreciate this function).&lt;/p&gt;

&lt;h3 id=&quot;set_error_handler&quot;&gt;set_error_handler&lt;/h3&gt;

&lt;p&gt;Another nifty trick is replacing the default PHP error handler with your own (using the function &lt;a href=&quot;http://php.net/manual/en/function.set-error-handler.php&quot;&gt;set_error_handler&lt;/a&gt;).  Typically I replace it with a custom handler that uses debug_backtrace  to give me a call stack and/or use var_dump to see what’s going on. Here is an example:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set_error_handler('debugErrorHandler');

// problem code here          

restore_error_handler();

// error handler function
//TODO: remove this after we figure out the problem
function debugErrorHandler($errno, $errstr, $errfile, $errline) {
	var_dump(debug_backtrace());
	// call php error handler
	return false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;warning-mysql_real_escape_string-expects-parameter-1-to-be-string-array-given-in-includesdatabasemysqlinc-on-line-321&quot;&gt;Warning: mysql_real_escape_string() expects parameter 1 to be string, array given in includes/database.mysql.inc on line 321&lt;/h3&gt;

&lt;p&gt;This error seems to end up all over the place and it makes a great “how to debug” story. A lot of times someone will write some ugly code,  forget about it, then push it to your development environment or provide  it via a module.&lt;/p&gt;

&lt;p&gt;What do you do if you start seeing this (or a similar type of error)  appearing in your watchdog logs? How can you figure out which module is  causing the problem?&lt;/p&gt;

&lt;p&gt;Simply put, you follow the &lt;a href=&quot;http://drupal.org/node/766256&quot;&gt;instructions here&lt;/a&gt;, except you replace the function with this one:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function db_escape_string($text) {
	global $active_db;
	if(is_array($text)) {
		var_dump(debug_backtrace())
	}

	return mysql_real_escape_string($text, $active_db);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Alright, well that’s about it. Happy debugging!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Helvetica</title>
   <link href="http://blog.hross.net/design/2010/03/10/helvetica.html"/>
   <updated>2010-03-10T00:00:00+00:00</updated>
   <id>http://blog.hross.net/design/2010/03/10/helvetica</id>
   <content type="html">&lt;p&gt;The best thing about Netflix is the instant queue and its constant ability to surprise me. I end up watching movies and documentaries about things I would normally never see, simply because I wouldn’t know they exist. One such example is the documentary &lt;a href=&quot;http://www.helveticafilm.com/&quot;&gt;Helvetica&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am sure there are many consultants, programmers and others in the IT world who, like me, end up wearing the graphic designer hat from time to time. It may be to create icons or images for a web site, or it may be to edit CSS styles.&lt;/p&gt;

&lt;p&gt;If you are one of these types of people, and you have even a passing interest in design, Helvetica is for you. It takes you down the rabbit hole of typeface design, various artistic movements, and the “why’s” and “how’s” of what make a good typeface. This is one of those movies that could actually make you better at your job and provide some interesting historical context around something we take for granted when we design web sites.&lt;/p&gt;

&lt;p&gt;It will also make you look at normal everyday signage in a totally different light.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Apache Directory</title>
   <link href="http://blog.hross.net/ldap/2010/03/05/apachedirectory.html"/>
   <updated>2010-03-05T00:00:00+00:00</updated>
   <id>http://blog.hross.net/ldap/2010/03/05/apachedirectory</id>
   <content type="html">&lt;p&gt;In one of my &lt;a href=&quot;/windows/utilities/2009/09/12/windowstools.html&quot;&gt;previous posts&lt;/a&gt; I mentioned &lt;a href=&quot;http://directory.apache.org/studio/&quot;&gt;Apache Directory Studio&lt;/a&gt; as a great way to view LDAP directories, but the entire &lt;a href=&quot;http://directory.apache.org/&quot;&gt;Apache Directory&lt;/a&gt; project really deserves its own post (and here it is).&lt;/p&gt;

&lt;p&gt;First of all, Apache has implemented a fully featured directory browser (&lt;a href=&quot;http://directory.apache.org/studio/&quot;&gt;Apache Directory Studio&lt;/a&gt;) and a fully featured LDAP directory (&lt;a href=&quot;http://directory.apache.org/apacheds/1.5/&quot;&gt;Apache Directory Server&lt;/a&gt;). Both of these projects are entirely Java based. Studio is an eclipse based directory browser with all the bells and whistles. &lt;a href=&quot;http://jxplorer.org/&quot;&gt;JXplorer&lt;/a&gt; is nice, but not as powerful or as easy to use.&lt;/p&gt;

&lt;p&gt;However, the real power of ApacheDS is the server. It’s entirely java based and is &lt;a href=&quot;http://directory.apache.org/apacheds/1.5/downloads.html&quot;&gt;available for a variety of platforms, including Windows&lt;/a&gt;. I have yet to find an easier platform to install and use on a variety of operating systems (especially Windows). Rather than trying to build a confusing &lt;a href=&quot;http://www.openldap.org/&quot;&gt;OpenLDAP&lt;/a&gt; implementation, you can simply download, install, and start ApacheDS in 5 to 10 minutes.&lt;/p&gt;

&lt;p&gt;And, oh by the way, you can also &lt;a href=&quot;http://cwiki.apache.org/DIRxSRVx10/embedding-apacheds-as-a-web-application.html&quot;&gt;embed and manipulate ApacheDS in your own applications&lt;/a&gt;, since its written entirely in Java and the source code is freely available.&lt;/p&gt;

&lt;p&gt;So, if you are looking for an easy, free, directory implementation for your next proof of concept, demo, or unit test, look no further than ApacheDS.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Google Translate is Awesome</title>
   <link href="http://blog.hross.net/google/2010/01/14/googletranslate.html"/>
   <updated>2010-01-14T00:00:00+00:00</updated>
   <id>http://blog.hross.net/google/2010/01/14/googletranslate</id>
   <content type="html">&lt;p&gt;I am not sure this is even enough for a blog post, but… well it is. Here are just a few the of cool things you can do with Google Translate:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Use the new audio capability to &lt;a href=&quot;http://weston.ruter.net/projects/google-tts/&quot;&gt;generate audio translations of text&lt;/a&gt;, automatically.&lt;/li&gt;
  &lt;li&gt;Use the translate API to &lt;a href=&quot;http://translate.google.com/translate_tools?hl=en&amp;amp;layout=1&amp;amp;eotf=1&quot;&gt;translate your entire web page by simply including some javascript&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Programmatically translate stuff using the &lt;a href=&quot;http://code.google.com/p/google-api-translate-java/&quot;&gt;unofficial Java API&lt;/a&gt;. There is probably one for .NET, but I haven’t tried it.&lt;/li&gt;
  &lt;li&gt;Translate bits and pieces of your web site, on demand, using a very cool jQuery plugin called &lt;a href=&quot;http://sundaymorning.jaysalvat.com/&quot;&gt;Sunday Morning&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Synchronizing Email with Ruby</title>
   <link href="http://blog.hross.net/ruby/2010/01/11/rubyemail.html"/>
   <updated>2010-01-11T00:00:00+00:00</updated>
   <id>http://blog.hross.net/ruby/2010/01/11/rubyemail</id>
   <content type="html">&lt;p&gt;Recently, I was in the midst of a Windows 7 installation when my company decided to migrate my email to a new mail server. As we in the IT world are aware, migrations rarely go as planned. It seemed like as good a time as any for me to start a project I have long considered: migrating all of my email to Gmail.&lt;/p&gt;

&lt;p&gt;I guess this is technically something I’m not supposed to do. Then again, it is no less secure than downloading and sending email using my local laptop and a standard email client (provided the passwords/accounts are properly encrypted). Either way, I love Gmail for personal email and there is no way my entire work organization is going to switch to Gmail, so I decided to set up my own little synchronization process.&lt;/p&gt;

&lt;p&gt;Here is what I did:&lt;/p&gt;

&lt;p&gt;1.) &lt;a href=&quot;http://mail.google.com/support/bin/answer.py?hl=en&amp;amp;answer=75725&quot;&gt;Enable IMAP on my Gmail account&lt;/a&gt;. My work email is already IMAP, so this let me drag and drop folders from one mail account to another using Thunderbird. Once all my folders were migrated, I only had to worry about new email in my inbox.&lt;/p&gt;

&lt;p&gt;2.) Set up a synchronization process from my work email to my Gmail account. Transferring mail itself is pretty simple. There is an &lt;a href=&quot;http://www.faqs.org/rfcs/rfc822.html&quot;&gt;RFC that defines what mail messages look like&lt;/a&gt;, so they are the same data from one mail account to the other. The trick is moving them automatically.&lt;/p&gt;

&lt;p&gt;Since I already have a Linux host that runs full time (this site), it seemed like my most sane option would be to write something that I could schedule using &lt;a href=&quot;http://en.wikipedia.org/wiki/Cron&quot;&gt;cron&lt;/a&gt;. Since I am a member of the &lt;a href=&quot;http://en.wikipedia.org/wiki/Cargo_cult_programming&quot;&gt;cargo cult&lt;/a&gt;, I thought I could pretty easily find something on Google written in Java.&lt;/p&gt;

&lt;p&gt;After some searching, though, it seemed like the best and simplest examples were all written in Ruby. Unfortunately, none of them did exactly what I wanted so I figured I would have to write a bit of code. Before I began this endeavor, I knew nothing about Ruby (yes, I am way behind), but it seemed like a good time to learn.&lt;/p&gt;

&lt;p&gt;I started off with some setup:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://wiki.dreamhost.com/Ruby&quot; title=&quot;http://wiki.dreamhost.com/Ruby&quot;&gt;http://wiki.dreamhost.com/Ruby&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://wiki.dreamhost.com/index.php/Ruby_on_Rails&quot; title=&quot;http://wiki.dreamhost.com/index.php/Ruby_on_Rails&quot;&gt;http://wiki.dreamhost.com/index.php/Ruby_on_Rails&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next I went with a few blogs/docs and some source from the beginnings of &lt;a href=&quot;http://github.com/rgrove/larch&quot;&gt;larch&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://ruby-doc.org/stdlib/libdoc/net/imap/rdoc/classes/Net/IMAP.html&quot; title=&quot;http://ruby-doc.org/stdlib/libdoc/net/imap/rdoc/classes/Net/IMAP.html&quot;&gt;http://ruby-doc.org/stdlib/libdoc/net/imap/rdoc/classes/Net/IMAP.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://wonko.com/post/ruby_script_to_sync_email_from_any_imap_server_to_gmail&quot; title=&quot;http://wonko.com/post/ruby_script_to_sync_email_from_any_imap_server_to_gmail&quot;&gt;http://wonko.com/post/ruby_script_to_sync_email_from_any_imap_server_to_gmail&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://codeclimber.blogspot.com/2008/06/using-ruby-for-imap-with-gmail.html&quot; title=&quot;http://codeclimber.blogspot.com/2008/06/using-ruby-for-imap-with-gmail.html&quot;&gt;http://codeclimber.blogspot.com/2008/06/using-ruby-for-imap-with-gmail.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While I like larch, it doesn’t delete from my inbox, has way more features than I need, and it is more object oriented (and thus harder to understand) than I would like. Since I am a Ruby novice, I wanted something simple that I could make sure was working. Here is what I came up with:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#! ~/run/bin/ruby&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'net/imap'&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Synchronizing mailboxes...&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# create destination imap&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IMAP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;imap.gmail.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;993&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my.account@gmail.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# create source imap&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IMAP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;imap.work.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;993&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my.account@work.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Logins complete. Checking source mailbox for mail.&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'INBOX'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;NOT&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;DELETED&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Found message: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message_id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'RFC822'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'FLAGS'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                  &lt;span class=&quot;s1&quot;&gt;'INTERNALDATE'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

        &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Transferring message with id: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message_id&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'INBOX'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'RFC822'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'FLAGS'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'INTERNALDATE'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

        &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Deleting message from source inbox.&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;+FLAGS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:Deleted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Transfer complete. Logging out.&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;done.&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Simple, huh? I set this up to run as a cron job every ten minutes and that’s all it took.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Windows Tools</title>
   <link href="http://blog.hross.net/windows/utilities/2009/09/12/windowstools.html"/>
   <updated>2009-09-12T00:00:00+00:00</updated>
   <id>http://blog.hross.net/windows/utilities/2009/09/12/windowstools</id>
   <content type="html">&lt;p&gt;It came out just in time for my birthday this year: &lt;a href=&quot;http://www.hanselman.com/blog/ScottHanselmans2009UltimateDeveloperAndPowerUsersToolListForWindows.aspx&quot;&gt;Scott Hanselman’s 2009 Windows Power Tools list&lt;/a&gt;. It is indeed a great list. If you are a developer working in Windows and have a few minutes to spare, you might just find something on that list that makes your life a million times easier.&lt;/p&gt;

&lt;p&gt;As some of you may know, I recently took a new job within Oracle, and as such I had the privilege of getting a brand new laptop for my birthday (okay, I happened to start the job the same week as my birthday, but it &lt;strong&gt;felt&lt;/strong&gt; like I got a birthday present). There is nothing quite like a fancy new laptop and a list of awesome tools and utilities to install on it.&lt;/p&gt;

&lt;p&gt;Anyway, since I’ve finally got it perfectly configured (never to be this fast or nice again), and since searching through the 2009 list is a bit of a grind, I thought I’d share my own, much smaller, list of tools I love (I’ll try to keep this not-so-development-oriented):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.pssoftlab.com/pshl_info.phtml&quot;&gt;PS Hot Launch&lt;/a&gt;&lt;/strong&gt; - I hate the start menu, and the quick start tray is never big enough for all my icons. Enter PS Hot Launch, where I can now keep all my frequently used programs &lt;strong&gt;and&lt;/strong&gt; bind hotkeys for startup. There are a lot of heavy duty hotkey managers and the like, but for my money (or lack thereof) this is the easiest, best, and most free version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://sourceforge.net/projects/console/&quot;&gt;Console&lt;/a&gt;&lt;/strong&gt; - I never use a dos prompt anymore. This one looks way cooler and supports more features (like easy copy and paste).&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;&lt;a href=&quot;http://gnuwin32.sourceforge.net/&quot;&gt;GNU Win32&lt;/a&gt;&lt;/strong&gt; - One of my biggest peeves about Windows is not being able to do *find .&lt;/td&gt;
      &lt;td&gt;xargs grep “blah”* and get results. I sometimes use &lt;a href=&quot;http://www.cygwin.com/&quot;&gt;cygwin&lt;/a&gt;, but this is much less overhead and more native. Thank you GNU.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.microsoft.com/downloads/details.aspx?FamilyID=bff59fcf-3148-40b8-a286-fe7274f6e4d8&quot;&gt;Microsoft TimeZone&lt;/a&gt;&lt;/strong&gt; - This is a weird one, but I find myself constantly having to check what time it is on the west coast, central, etc.. I am never sure how many hours to subtract. This super simple free utility runs in your tray and lets you customize up to five locales to show current times for when you click on it. A lot easier than googling “current time pacific”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://desktop.google.com/&quot;&gt;Google Desktop&lt;/a&gt;&lt;/strong&gt; - When it first came out, I thought “will I really use this?” Now I can’t go a day without it. How many times have you thought about an email you sent two months ago but could only remember various key words? Or an old coding project that has something specific you did that you now can’t remember? I can’t even count the number of hours this has saved me in “hunting for stuff” time. Seriously… if you don’t have this you don’t even know what you’re missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.textpad.com/&quot;&gt;Textpad&lt;/a&gt;&lt;/strong&gt; - My favorite of the “enhanced text editors” crowd. It has a vast array of pluggable syntax highlighting (no more Eclipse to edit one line of Java code), explorer shell integration, and an intuitive interface without a ton of annoying bells and whistles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.postbox-inc.com/&quot;&gt;Postbox&lt;/a&gt;&lt;/strong&gt; - A lightweight email client. If it had better calendar integration I’d give it a gold star. Still a great quick and dirty client that has some nice search capability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.chiark.greenend.org.uk/~sgtatham/putty/&quot;&gt;PuTTY&lt;/a&gt;&lt;/strong&gt; - Remember when people made applications that fit in a single executable and just did the job? Yeah… I do too. If you’re not using PuTTY I have no idea why not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.straightrunning.com/XmingNotes/&quot;&gt;Xming&lt;/a&gt;&lt;/strong&gt; - Perhaps a limited audience on this one, but if you need to use X in Windows, this one is for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.oracle.com/technology/products/database/sql_developer&quot;&gt;SQLDeveloper&lt;/a&gt;&lt;/strong&gt; - Wait… you mean to tell me Oracle made a lightweight, user friendly, super powerful database tool? And I don’t have to use sqlplus or &lt;a href=&quot;http://www.toadsoft.com/&quot;&gt;Toad&lt;/a&gt; anymore? And it’s free?! Say no more…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://directory.apache.org/studio/&quot;&gt;Apache Directory Studio/Server&lt;/a&gt;&lt;/strong&gt; - I absolutely love these two. I used to use &lt;a href=&quot;http://www.ldapbrowser.com/&quot;&gt;Softerra LDAP Browser&lt;/a&gt;, which is a great tool in its own right, but I cannot tell you how happy I am to see an easy to install, easy to configure LDAP directory for Windows. I can now test LDAP integrations with impunity! Documentation is a bit sparse, but the first time I downloaded and installed this sucker I got all warm and fuzzy inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://winmerge.org/&quot;&gt;WinMerge&lt;/a&gt;&lt;/strong&gt; - I blogged about this earlier and it is great for file and directory comparisons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.pdfsam.org/&quot;&gt;PDF Split and Merge&lt;/a&gt;&lt;/strong&gt; - This is a great tool if you find yourself having to create expense reports or combine PDF’s of scanned documents. The name says it all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://they.misled.us/dark-room&quot;&gt;Darkroom&lt;/a&gt;&lt;/strong&gt; - I tend to take a lot of notes in text editors and this one just looks awesome. You may not use it all the time, but it definitely gets an A+ on style points.&lt;/p&gt;

&lt;p&gt;Here are a few more that are pretty common so I won’t write blurbs about them: &lt;a href=&quot;http://www.pidgin.im/&quot;&gt;Pidgin&lt;/a&gt;, &lt;a href=&quot;http://www.imgburn.com/&quot;&gt;ImgBurn&lt;/a&gt;, &lt;a href=&quot;http://www.getpaint.net/&quot;&gt;PaintDotNet&lt;/a&gt;, &lt;a href=&quot;http://download.live.com/writer&quot;&gt;LiveWriter&lt;/a&gt;, and &lt;a href=&quot;http://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt;. And of course there are the myriad plugins you can get for Firefox, but they deserve a separate post.&lt;/p&gt;

&lt;p&gt;Alright… happy downloading!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Sample Search Portlets</title>
   <link href="http://blog.hross.net/search/2009/09/03/samplesearchportlets.html"/>
   <updated>2009-09-03T00:00:00+00:00</updated>
   <id>http://blog.hross.net/search/2009/09/03/samplesearchportlets</id>
   <content type="html">&lt;p&gt;A while back I wrote &lt;a href=&quot;/jquery/dwr/2009/05/11/dwrjquerytables.html&quot;&gt;this post&lt;/a&gt; detailing my struggle to find a quick and dirty way of displaying paginated table data in the portal (or anywhere, for that matter). I ended up settling on the method I &lt;a href=&quot;http://www.spartanjava.com/2008/paginated-lists-made-really-easy-part-1-of-2-front-end/&quot;&gt;found at Spartan Java&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While I enjoyed the series of articles, I still ended up having to review a &lt;a href=&quot;http://dotnetslackers.com/articles/ajax/JQuery-Primer-Part-1.aspx&quot;&gt;jQuery primer&lt;/a&gt;, the &lt;a href=&quot;http://directwebremoting.org/dwr/documentation.html&quot;&gt;DWR documentation&lt;/a&gt; and the &lt;a href=&quot;http://svn.apache.org/repos/asf/ibatis/java/ibatis-3/trunk/doc/en/iBATIS-3-User-Guide.pdf&quot;&gt;iBATIS user guide&lt;/a&gt;. I would have liked to have been able to download the finished application. On top of that there were the usual struggles with portletizing the code, and scrambling to jam a bunch of features into the finished web application before launch.&lt;/p&gt;

&lt;p&gt;Thus, when I got asked to provide some “development best practices” portal code, I was in a bit of a bind. I wanted to show off the WCI portal’s ability to consume and use a variety of frameworks, but my original source was… pretty crappy. In the end I went back and rewrote a very simple sample application from scratch (including documentation) using the methods described above.&lt;/p&gt;

&lt;p&gt;Hopefully I can save you some time and effort in similar endeavors. Without further ado here is a link to a &lt;a href=&quot;/assets/searchsample.war&quot;&gt;sample database search application&lt;/a&gt; using the &lt;a href=&quot;http://www.oracle.com/technology/obe/obe1013jdev/common/OBEConnection.htm&quot;&gt;Oracle HR sample data&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’s nothing fancy (yes, there are even possible SQL injection vulnerabilities), but if you want a simple example of jQuery, iBATIS, Java and the portal, this is definitely something to check out. All you should have to do is import the war file into eclipse and read the index.html documentation.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Configuring Oracle XE</title>
   <link href="http://blog.hross.net/oracle/2009/08/27/oraclexe.html"/>
   <updated>2009-08-27T00:00:00+00:00</updated>
   <id>http://blog.hross.net/oracle/2009/08/27/oraclexe</id>
   <content type="html">&lt;p&gt;Lately I have found myself setting up a lot of Amazon EC2 instances, new computers, virtual machines and the like. As such, I’ve come to know and love &lt;a href=&quot;http://www.oracle.com/technology/products/database/xe/index.html&quot;&gt;Oracle XE&lt;/a&gt;. While it “just works”, there are a few tweaks that vastly improve performance and behavior. All of these tweaks require you to log in as the system user and run the noted SQL. In no particular order, they are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Modifying the default listening port of the web server (why is it listening on the default Tomcat port?!): 
    begin
    	dbms_xdb.sethttpport(‘7080’);
    	dbms_xdb.setftpport(‘2100’);
    end;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Increasing the number of sessions and processes so you don’t get locked out of the database: 
    alter system set sessions=250 scope=spfile;
    alter system set processes=200 scope=spfile;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Making the web server available for non-local access (in case you are running out of a console): 
    begin 
    	dbms_xdb.setlistnerlocalaccess(false); 
    end;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here are the original links for these tips:&lt;/p&gt;

&lt;p&gt;Finally, I have a tip of my own. If you are running Oracle XE in an EC2 instance (&lt;a href=&quot;http://www.oracle.com/technology/pub/articles/lokitz-cloud.html&quot;&gt;see this article&lt;/a&gt;), you will undoubtedly notice that when you restart your brand new AMI with a new IP, Oracle will fail to start (doh!). In order to fix this, you need to do the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Make copies of your listener.ora and tnsnames.ora files. Modify them so that your current hostname in EC2 is replaced with “localhost”, and rename them to listener.ora.localhost and tnsnames.ora.localhost.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Add this script to the /etc/init.d/oracle-xe startup script (under start is preferable): 
    NEWHOST=&lt;code class=&quot;highlighter-rouge&quot;&gt;hostname&lt;/code&gt;&lt;/p&gt;

    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sed s/localhost/$NEWHOST/ /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/network/admin/listener.ora.localhost
&amp;gt; /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/network/admin/listener.ora
sed s/localhost/$NEWHOST/ /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/network/admin/tnsnames.ora.localhost
&amp;gt; /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/network/admin/tnsnames.ora
&lt;/code&gt;&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Profit (you may want to modify the above to run as the oracle user – not as root).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(sorry Windows users, you will have to create your own variation of this)&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>EC2/Cloudwatch Gaming Results</title>
   <link href="http://blog.hross.net/ec2/2009/05/31/ec2cloudwatch.html"/>
   <updated>2009-05-31T00:00:00+00:00</updated>
   <id>http://blog.hross.net/ec2/2009/05/31/ec2cloudwatch</id>
   <content type="html">&lt;p&gt;As I mentioned in &lt;a href=&quot;/ec2/2009/05/10/ec2gameserver.html&quot;&gt;my previous post&lt;/a&gt;, I wanted to capture some real world info on hosting a game server in the cloud. The results were a rousing success. We had 5 or 6 people connected at various times, played some Deathmatch and Capture the Flag, and everyone had a ping of 40 or less the entire time. I didn’t notice any latency whatsoever and there were absolutely no packet loss or lag complaints throughout.&lt;/p&gt;

&lt;h3 id=&quot;cost&quot;&gt;Cost&lt;/h3&gt;

&lt;p&gt;I haven’t broken down the numbers yet, but all told I started up an EC2 instance and hosted a game for 2 hours. I also attached an elastic IP for ease of use. That cost me less than $0.50. I’d say that’s a pretty good deal.&lt;/p&gt;

&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;

&lt;p&gt;Below are the usage stats for network I/O and CPU usage. I gathered these using &lt;a href=&quot;/ec2/2009/05/19/cloudwatch.html&quot;&gt;my simple Java application&lt;/a&gt; and created these no-frills charts in Microsoft Excel (all told, this took about 5 minutes to put together):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ec2_network_io.png&quot; alt=&quot;network io&quot; /&gt; 
Figure 1 - Network I/O over a 2 hour F.E.A.R. game&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ec2_cpu_usage.png&quot; alt=&quot;cpu usage&quot; /&gt;
Figure 2 - CPU Usage over a 2 hour F.E.A.R. game&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;This is a short and imperfect analysis, but overall I’d say the “small” EC2 instance could easily have handled a 16 person game, both from a load and network traffic standpoint, and it would have cost me a dollar or so to host for 2 hours. That seems like great bang for your buck if you’re looking to crank up a quick game and then move on to something else.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Update Publisher Publishing Targets</title>
   <link href="http://blog.hross.net/webcenter/2009/05/21/updatingpublishertargets.html"/>
   <updated>2009-05-21T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2009/05/21/updatingpublishertargets</id>
   <content type="html">&lt;p&gt;I have recently been working on a utility for porting ALUI databases from a production environment to a development environment. &lt;a href=&quot;http://fsanglier.blogspot.com/&quot;&gt;Fabien Sanglier&lt;/a&gt; started this effort, and I hope to have some code to contribute to his &lt;a href=&quot;http://code.google.com/p/alui-toolbox/&quot;&gt;ALUI toolbox project&lt;/a&gt; very soon.&lt;/p&gt;

&lt;p&gt;In the meantime, however, I have been banging my head against the pain that is migrating Publish and Preview target URL’s in Publisher. These URL’s are stored in a binary BLOB in the Publisher database, and are actually serialized Java classes, making them extremely difficult to update (especially when you don’t have access to the original Publisher source code).&lt;/p&gt;

&lt;p&gt;My original plan was to wrap all of this stuff into one “uber-utility” and then blog about it. Recently, though, I saw this post on the Oracle Webcenter Interaction discussion forums: &lt;a href=&quot;http://forums.oracle.com/forums/thread.jspa?threadID=900736&amp;amp;tstart=0&quot; title=&quot;http://forums.oracle.com/forums/thread.jspa?threadID=900736&amp;amp;tstart=0&quot;&gt;http://forums.oracle.com/forums/thread.jspa?threadID=900736&amp;amp;tstart=0&lt;/a&gt; and it made me think I should probably post the code for migrating Publishing Targets, for the benefit of the sanity of the community at large.&lt;/p&gt;

&lt;p&gt;Here is a &lt;a href=&quot;/assets/updatepublishtargets.jar&quot;&gt;link to a jar file which will update Publisher publish targets&lt;/a&gt;. If you crack the jar file with a zip editor, you will be able to update the &lt;em&gt;configuration.properties&lt;/em&gt; file in the root directory to suit your needs.&lt;/p&gt;

&lt;p&gt;I took the liberty of including the Publisher classes in my own jar, making it simpler to run from a command line. To run it, you will only need to download the correct jdbc driver for your database:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html&quot;&gt;Oracle JDBC Driver&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://msdn.microsoft.com/en-us/data/aa937724.aspx&quot;&gt;SQL Server JDBC Driver&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, simply execute it from a java command line with the driver in your classpath, like so:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java -cp updatepublishtargets.jar;ojdbc14.jar net.hross.content.UpdatePublishTargets
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Note that the utility is in debug mode by default, so nothing will happen to your Publisher database until you set debug to false in the configuration, although now is probably a good time to let you know that &lt;strong&gt;&lt;em&gt;I provide no warranties of any kind with this code&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In order to build and run the source, you will need the &lt;em&gt;content.jar&lt;/em&gt; and &lt;em&gt;dom4j.jar&lt;/em&gt; found in the WEB-INF/lib directory of your &lt;em&gt;ptcs.war&lt;/em&gt;. Here is the relevant source code, in case you are looking to build your own version of the utility (source is also in the jar):&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package net.hross.content;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import net.hross.utility.Configuration;

import com.plumtree.content.data.AttributeKey;
import com.plumtree.content.data.impl.RdbiPublishingTarget;

public class UpdatePublishTargets {

    public static void main(String[] args) {
        Connection connection = Configuration.getConnection();

        if (null == connection) {
            System.out.println(&quot;Unable to connect to database. Exiting.&quot;);
        }

        int directoryId = Integer.parseInt(Configuration
                .getString(Configuration.CONFIG_DIRECTORY_ID));
        boolean debug = Boolean.parseBoolean(Configuration
                .getString(Configuration.CONFIG_DEBUG_MODE));
        String newPublishTarget = Configuration
                .getString(Configuration.CONFIG_PUBLISH_TARGET);

        System.out.println(&quot;Updating publish targets for directory ID: &quot;
                + directoryId);

        System.out.println();
        if (debug) {
            System.out.println(&quot;** DEBUG MODE ON ** Nothing will be updated.&quot;);
        } else {
            System.out.println(&quot;** DEBUG MODE OFF ** This is happening for real.&quot;);
        }
        System.out.println();

        updatePublishTarget(connection, directoryId, newPublishTarget, debug);
    }

    /***
     * Update the publishing target for a specified directory ID (-1 for all
     * items).
     * 
     * @param connection
     *            Publisher database connection.
     * @param directoryId
     *            Directory ID to update. -1 for all items.
     * @param newPublishTarget
     *            - New publishing target.
     * @param debug
     *            - if true, no replace will be made, data will just be output.
     */
    public static void updatePublishTarget(Connection connection,
            int directoryId, String newPublishTarget, boolean debug) {

        // create a statement to query the directory id
        try {

            // create prepared statement for directory query
            PreparedStatement psDirectory = null;
            if (directoryId &amp;amp;gt; 0) {
                psDirectory = connection
                        .prepareStatement(&quot;SELECT * FROM PCSDIRECTORY WHERE ITEMTYPE=0 AND DIRECTORYID=?&quot;);
                psDirectory.setInt(1, directoryId);
            } else {
                psDirectory = connection
                        .prepareStatement(&quot;SELECT * FROM PCSDIRECTORY WHERE ITEMTYPE=0&quot;);
            }
            ResultSet rs = psDirectory.executeQuery();

            // loop through any rows we need to check
            while (rs.next()) {

                // get basic info about the object
                String itemName = rs.getString(&quot;ITEMNAME&quot;);
                int size = rs.getInt(&quot;DATASIZE&quot;);
                
                // reset directory ID in case it was generic
                directoryId = rs.getInt(&quot;DIRECTORYID&quot;);

                // get binary input stream
                InputStream input = rs.getBinaryStream(&quot;DATABYTES&quot;);

                // if there's actually some settings, let's check them
                if ((null != input) &amp;amp;amp;&amp;amp;amp; (0 != size)) {

                    // generic catch statement for problems with this item
                    try {
                        byte[] buffer = new byte[size];
                        input.read(buffer);

                        // load the hash map from the database
                        Map map = (HashMap) deserialize(buffer);

                        // loop through the keys in the hash map
                        Iterator keys = map.keySet().iterator();
                        while (keys.hasNext()) {
                            Object key = keys.next();

                            // this should probably always be true
                            if (key.getClass().equals(AttributeKey.class)) {
                                AttributeKey akey = (AttributeKey) key;

                                // if we found a publishing target...
                                if (akey.getKeyString().equals(
                                        &quot;PUBLISHING_TARGET&quot;)) {
                                    System.out.println();
                                    System.out.println(&quot;--------------------&quot;);
                                    System.out
                                            .println(&quot;Updating publishing target for:&quot;);
                                    System.out.println(directoryId + &quot; - &quot;
                                            + itemName);

                                    // get the publishing target info
                                    RdbiPublishingTarget val = (RdbiPublishingTarget) map
                                            .get(key);
                                    String publishTarget = val
                                            .getPublishDetail()
                                            .getTargetLocation();
                                    String publishBrowser = val
                                            .getPublishDetail()
                                            .getBrowserLocation();
                                    String previewTarget = val
                                            .getPreviewDetail()
                                            .getTargetLocation();
                                    String previewBrowser = val
                                            .getPreviewDetail()
                                            .getBrowserLocation();
                                    String ftpUser = val.getPublishDetail()
                                            .getUsername();
                                    String ftpPassword = val.getPublishDetail()
                                            .getPassword();

                                    System.out
                                            .println(&quot;Publish  browser location: &quot;
                                                    + publishBrowser);
                                    System.out.println(&quot;Preview target: &quot;
                                            + previewTarget);
                                    System.out
                                            .println(&quot;Preview browser location: &quot;
                                                    + previewBrowser);
                                    System.out.println(&quot;FTP user: &quot; + ftpUser);
                                    System.out.println(&quot;FTP password: &quot;
                                            + ftpPassword);
                                    System.out.println(&quot;Old publish target: &quot;
                                            + publishTarget);
                                    System.out.println(&quot;New publish target: &quot;
                                            + newPublishTarget);

                                    // if we are doing this for real, update
                                    // values
                                    if (!debug) {
                                        val.setTargetValues(newPublishTarget,
                                                publishBrowser, previewTarget,
                                                previewBrowser, ftpUser,
                                                ftpPassword);

                                        map.put(key, val);

                                        // update the directory
                                        serializeToDirectory(connection,
                                                directoryId, map);
                                        System.out.println(&quot;Update successful.&quot;);
                                    }
                                    System.out.println(&quot;--------------------&quot;);
                                    System.out.println();
                                }
                            }
                        }

                        // clean up
                        input.close();
                    } catch (IOException ex) {
                        System.out.println(&quot;Something bad happened.&quot;);
                        ex.printStackTrace();
                    }
                } // if null
            } // while next rs
        } catch (SQLException ex) {
            System.out.println(&quot;Something bad happened.&quot;);
            ex.printStackTrace();
        }

        System.out.println(&quot;Procedure successfully completed.&quot;);
    }

    private static Object deserialize(byte bytes[]) {
        try {
            ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
            ObjectInputStream objectStream = new ObjectInputStream(byteStream);
            return objectStream.readObject();
        } catch (Exception ex) {
            return null;
        }
    }

    private static void serializeToDirectory(Connection conn, int directoryId,
            Object obj) throws IOException, SQLException {
        byte bytes[] = getBytes(obj);
        ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);

        PreparedStatement ps = conn
                .prepareStatement(&quot;UPDATE PCSDIRECTORY SET DATASIZE=?, DATABYTES=? WHERE DIRECTORYID=?&quot;);
        ps.setInt(1, bytes.length);
        ps.setBinaryStream(2, byteStream, bytes.length);
        ps.setInt(3, directoryId);
        ps.execute();
        conn.commit();
    }

    public static byte[] getBytes(Object obj) throws java.io.IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(obj);
        oos.flush();
        oos.close();
        bos.close();
        byte[] data = bos.toByteArray();
        return data;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Monitoring Performance with Amazon CloudWatch</title>
   <link href="http://blog.hross.net/ec2/2009/05/19/cloudwatch.html"/>
   <updated>2009-05-19T00:00:00+00:00</updated>
   <id>http://blog.hross.net/ec2/2009/05/19/cloudwatch</id>
   <content type="html">&lt;p&gt;It is rare that I am on the bleeding edge of technology. Normally, I don’t think its worth the time and effort necessary to learn something brand new unless it has been at least somewhat widely adopted and accepted by the community at large.&lt;/p&gt;

&lt;p&gt;Oddly enough, my blog post about &lt;a href=&quot;/ec2/2009/05/10/ec2gameserver.html&quot;&gt;running a game server on EC2&lt;/a&gt; turned out to be perfectly timed, as Amazon &lt;a href=&quot;http://aws.amazon.com/about-aws/whats-new/2009/05/17/monitoring-auto-scaling-elastic-load-balancing/&quot;&gt;launched its new CloudWatch, Elastic Scaling and Load Balancing services on Sunday&lt;/a&gt;. And since, as I discussed earlier, I have been looking at ways to monitor the usage of my EC2 game server, I somehow find myself on the bleeding edge of the cloud.&lt;/p&gt;

&lt;h3 id=&quot;why-cloudwatch&quot;&gt;Why CloudWatch?&lt;/h3&gt;

&lt;p&gt;As I discussed in my &lt;a href=&quot;/ec2/2009/05/10/ec2gameserver.html&quot;&gt;previous post&lt;/a&gt;, setting up monitoring on an EC2 instance wasn’t that hard to do. However, it did come with some drawbacks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Maintenance&lt;/strong&gt; “ Although it can be fun to install new software and learn its in’s and out’s, the actual task of upgrading that software, maintaining it, patching it, watching it for security risks, etc, etc is a major pain in the rear end. &lt;a href=&quot;http://aws.amazon.com/cloudwatch/&quot;&gt;CloudWatch&lt;/a&gt; solves this problem by providing a simple service for retrieving performance data, no maintenance or special setup required.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Granularity&lt;/strong&gt; “ As I discovered with &lt;a href=&quot;http://munin.projects.linpro.no/&quot;&gt;munin&lt;/a&gt;, there are limitations to the frequency with which you can store performance data, not to mention the storage requirements for vast quantities of it. Again, this is hidden from us in the case of &lt;a href=&quot;http://aws.amazon.com/cloudwatch/&quot;&gt;CloudWatch&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt; “ Last but certainly not least, monitoring something usually incurs a performance hit. In my previous article I was sampling data on the same host I was tracking statistics from. The very act of collecting performance data could cause that data to be skewed. Since &lt;a href=&quot;http://aws.amazon.com/cloudwatch/&quot;&gt;CloudWatch&lt;/a&gt; abstracts this away from individual instances, this is no longer a problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;getting-started-with-cloudwatch&quot;&gt;Getting Started With CloudWatch&lt;/h3&gt;

&lt;p&gt;There are quite a few resources available to get you started with CloudWatch. I recommend taking a look at the &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=2521&amp;amp;categoryID=187&quot;&gt;javascript scratch pad&lt;/a&gt; and the other various &lt;a href=&quot;http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=187&quot;&gt;developer libraries already available&lt;/a&gt; (more on this later).&lt;/p&gt;

&lt;p&gt;If you really want to get down to the nitty gritty, you should start with the CloudWatch command line interface (CLI). Here are some simple steps to get you started:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Download the &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=351&amp;amp;categoryID=88&quot;&gt;EC2 API Tools&lt;/a&gt; first (you’ll need them to set up monitoring). Check out the &lt;a href=&quot;http://docs.amazonwebservices.com/AWSEC2/2008-12-01/GettingStartedGuide/&quot;&gt;Getting Started Guide&lt;/a&gt; for instructions on extracting the tools and setting up the proper environment variables.&lt;/li&gt;
  &lt;li&gt;Download the &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=2534&amp;amp;categoryID=88&quot;&gt;CloudWatch API Tools&lt;/a&gt;. Check out the included readme for details on environment variable setup.&lt;/li&gt;
  &lt;li&gt;Start up an EC2 instance like you normally would (see my &lt;a href=&quot;/ec2/2009/05/10/ec2gameserver.html&quot;&gt;previous post&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;Enable monitoring on your running instance using the EC2 API Tools command: &lt;em&gt;ec2-monitor-instances .&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Take a look at the &lt;a href=&quot;http://docs.amazonwebservices.com/AmazonCloudWatch/latest/DeveloperGuide/index.html&quot;&gt;CloudWatch Getting Started Guide&lt;/a&gt; for details on the available monitoring parameters, etc.&lt;/li&gt;
  &lt;li&gt;Run the CloudWatch command &lt;em&gt;mon-get-stats&lt;/em&gt; to get some statistics from your running instance (&lt;em&gt;mon-get-stats “help&lt;/em&gt; should give you some examples).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here are a few things to keep in mind when running the command line utility:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;I normally output data to a CSV file so I can create fancy graphs in Excel. Here is an example command (Windows) that delimits stats by comma and outputs to a CSV file: 
    mon-get-stats CPUUtilization –start-time 2009-05-19T21:00:00
     –end-time 2009-05-19T22:00:00 –period 60 –statistics Average 
    –namespace AWS/EC2 –delimiter “,” 
    –dimensions “InstanceId=i-2bb5cc42” &amp;gt; stats.csv&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Timestamps “ As per the forums, input timestamps are in &lt;a href=&quot;http://en.wikipedia.org/wiki/ISO_8601&quot;&gt;ISO-8601&lt;/a&gt; format with the default timezone UTC (Eastern Standard Time + 4 hours). Output timestamps are in UTC and cannot be changed (so start thinking in Greenwich Mean Time).&lt;/li&gt;
  &lt;li&gt;Virtually as soon as monitoring is enabled, statistics are retrieved from your instances. Data is available up to a per-minute frequency and is stored for two weeks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;writing-a-simple-java-monitoring-utility&quot;&gt;Writing a Simple Java Monitoring Utility&lt;/h3&gt;

&lt;p&gt;As much fun as I was having trying to parse and decipher various command line inputs, I was somewhat disappointed in the output. For one thing, there was the time formatting problem. For another, only one set of statistics (CPU utilization, network I/O, etc) were available at one time.&lt;/p&gt;

&lt;p&gt;I am not one to do more work than I need to, so instead of setting off to invent an uber-utility for aggregating data, I simply downloaded the &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=2517&amp;amp;categoryID=187&quot;&gt;Java library for CloudWatch&lt;/a&gt; and hacked up some of the sample code until I had a very basic utility for downloading and aggregating the data I wanted. I present it below in case someone finds it useful:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;

import com.amazonaws.cloudwatch.AmazonCloudWatch;
import com.amazonaws.cloudwatch.AmazonCloudWatchClient;
import com.amazonaws.cloudwatch.AmazonCloudWatchException;
import com.amazonaws.cloudwatch.model.Datapoint;
import com.amazonaws.cloudwatch.model.GetMetricStatisticsRequest;
import com.amazonaws.cloudwatch.model.GetMetricStatisticsResponse;
import com.amazonaws.cloudwatch.model.GetMetricStatisticsResult;

public class GrabStats {

    public static void main(String[] args) {
        
        String fileName = &quot;C:\stats.csv&quot;;

        String startTime = &quot;2009-05-19T20:00:00&quot;;
        String endTime = &quot;2009-05-20T00:00:00&quot;;
        
        String[] statList = { &quot;CPUUtilization&quot;,&quot;NetworkIn&quot;,&quot;NetworkOut&quot; }; //(%, bytes, bytes)
        
        HashMap&amp;amp;gt; map = new HashMap&amp;amp;gt;();
        
        // grab stats for each stat value
        for (int i = 0; i  stats = getStatistics(startTime, endTime, statList[i]);
            map.put(statList[i], stats);
        }
        
        // write to disk
        try {
            FileWriter fw = new FileWriter(fileName);
            
            // write the header
            fw.write(&quot;Date&quot;);
            for (int i = 0; i &quot;,&quot;);
                fw.write(statList[i]);
            }
            fw.write(&quot;n&quot;);
            
            // get a date iterator from our first statistic
            Iterator dateIterator = map.get(statList[0]).keySet().iterator();

            while(dateIterator.hasNext()) {
                String date = dateIterator.next();
                fw.write(date);
                
                // get values for each stat at this date
                for (int i = 0; i value = map.get(statList[i]).get(date);
                    fw.write(&quot;,&quot;);
                    fw.write(value.toString());
                }
                
                fw.write(&quot;n&quot;);
            }
            
            fw.close();
        } catch (IOException ex) {
            // error storing data
            System.out.print(&quot;Error writing file: &quot; + fileName);
        }

    }

    // define the cloudwatch service (should be a singleton)
    private static final String _accessKeyId = &quot;&quot;;
    private static final String _secretAccessKey = &quot;&quot;;
    private static AmazonCloudWatch _service = new AmazonCloudWatchClient(
            _accessKeyId, _secretAccessKey);

    public static HashMap getStatistics(String startTime,
            String endTime, String statName) {
        HashMap map = new HashMap();

        // build the request with some defaults
        GetMetricStatisticsRequest request = new GetMetricStatisticsRequest();
        ArrayList stats = new ArrayList();
        stats.add(&quot;Average&quot;);
        request.setStartTime(startTime);
        request.setEndTime(endTime);
        request.setPeriod(60); // statistics every minute
        request.setMeasureName(statName);
        request.setNamespace(&quot;AWS/EC2&quot;);
        request.setStatistics(stats);

        try {

            GetMetricStatisticsResponse response = _service
                    .getMetricStatistics(request);

            if (response.isSetGetMetricStatisticsResult()) {
                GetMetricStatisticsResult getMetricStatisticsResult = response
                        .getGetMetricStatisticsResult();
                java.util.List datapointsList = getMetricStatisticsResult
                        .getDatapoints();
                for (Datapoint datapoints : datapointsList) {
                    map.put(datapoints.getTimestamp(), datapoints.getAverage());
                }
            }

        } catch (AmazonCloudWatchException ex) {

            System.out.println(&quot;Caught Exception: &quot; + ex.getMessage());
            System.out.println(&quot;Response Status Code: &quot; + ex.getStatusCode());
            System.out.println(&quot;Error Code: &quot; + ex.getErrorCode());
            System.out.println(&quot;Error Type: &quot; + ex.getErrorType());
            System.out.println(&quot;Request ID: &quot; + ex.getRequestId());
            System.out.print(&quot;XML: &quot; + ex.getXML());
        }

        return map;
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;The CloudWatch tools and utilities are nothing less than I’d expect from Amazon. Everything worked as expected, the documentation was well put together and there were no real surprises with the API. Overall, I am very satisfied with the finished product of my meager efforts.&lt;/p&gt;

&lt;p&gt;There are, of course, a few shortcomings:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It would be nice to have more statistics available (memory usage being the main one I’m thinking of). Having the ability to define and collect your own statistics via an API would be even better. Since the API already has a flexible way of defining statistic and type, I have to assume this is coming.&lt;/li&gt;
  &lt;li&gt;Output visualization is certainly lacking. It would be great to see someone hack a Google Chart generator into the javascript scratch pad (given my lack of copious amounts of free time, this person won’t be me).&lt;/li&gt;
  &lt;li&gt;Adding some statistic collection and enablement to ElasticFox would certainly make things easier to set up and administer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I have to assume these drawbacks will be addressed in future updates, as they have been in the past. I am willing to accept them as the price to pay for being on the bleeding edge of the cloud.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Direct Web Remoting, jQuery and Tables</title>
   <link href="http://blog.hross.net/jquery/dwr/2009/05/11/dwrjquerytables.html"/>
   <updated>2009-05-11T00:00:00+00:00</updated>
   <id>http://blog.hross.net/jquery/dwr/2009/05/11/dwrjquerytables</id>
   <content type="html">&lt;p&gt;I recently came across a project where I had a need to display the results of a large SQL query in an HTML table using Java. Of course, I wanted to paginate it, style it, use AJAX to update it, and avoid the need for bulky toolkits or large frameworks. Oh yeah, I was also on an extremely tight deadline (&lt;em&gt;read:&lt;/em&gt; proper coding and design principles were not used).&lt;/p&gt;

&lt;p&gt;I looked into a couple of options:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://displaytag.sourceforge.net/1.2/&quot;&gt;Display Tag&lt;/a&gt; “ I used this for another project, but it uses session variables to paginate and doesn’t lend itself well to AJAX updates.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://code.google.com/webtoolkit/&quot;&gt;GWT&lt;/a&gt; “ One of the guys over at &lt;a href=&quot;http://www.function1.com&quot;&gt;Function1&lt;/a&gt; used it recently and it looked pretty slick. Unfortunately, it seemed like a lot of overhead and a styling headache for simple “display a table” functionality.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://directwebremoting.org/&quot;&gt;DWR&lt;/a&gt; and &lt;a href=&quot;http://jquery.com/&quot;&gt;jQuery&lt;/a&gt; “ As it turns out, I found a great series of blog posts (&lt;a href=&quot;http://www.spartanjava.com/2008/paginated-lists-made-really-easy-part-1-of-2-front-end/&quot;&gt;part 1&lt;/a&gt;, &lt;a href=&quot;http://www.spartanjava.com/2008/paginated-lists-made-really-easy-part-2-of-2-back-end/&quot;&gt;part 2&lt;/a&gt;) over at Spartan Java that pretty much laid out a solution to my problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see from the posts on Spartan Java, creating the framework to display SQL results using DWR and jQuery was simple, fast, and fairly straightforward. Because the poster makes some assumptions about your knowledge of DWR and jQuery, I would suggest combining the above with the &lt;a href=&quot;http://directwebremoting.org/dwr/getstarted&quot;&gt;getting started guide for DWR&lt;/a&gt; and the &lt;a href=&quot;http://docs.jquery.com/How_jQuery_Works&quot;&gt;jQuery tutorials&lt;/a&gt;, if you are unfamiliar with either.&lt;/p&gt;

&lt;p&gt;If you know basic DHTML and Java the learning curve should be no problem.&lt;/p&gt;

&lt;h3 id=&quot;dwr-and-the-portal&quot;&gt;DWR and the Portal&lt;/h3&gt;

&lt;p&gt;As usual, the code I was writing was eventually destined to show up in the portal. Because jQuery is just a simple javascript library, it works great in the portal without issue. Unfortunately, DWR, like many other AJAX frameworks, has its issues when it is gatewayed.&lt;/p&gt;

&lt;p&gt;After combing through the dustier nooks of the documentation, googling profusely, and downloading the source code, I discovered the secret to making DWR work. Since some of you may be thinking “wow, DWR looks like something I want to use in my next portlet”, I thought I’d elaborate:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Add an anchor tag to whatever portlet you will eventually display in the portal. This anchor tag should have an id (let’s call it “gatewaybase”) and it should reference the base path of DWR in your application (this will almost always be /dwr/). So, for any portlet I want to use DWR in, I would always have the following (this is in a JSP, you might need to change to another base path): 
script src=”dwr/interface/ClassNameExample.js”&amp;gt;script&amp;gt;
&amp;gt;script&amp;gt;
a id=”gatewaybase” target=”/dwr/” href=”/dwr/”&amp;gt;a&amp;gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The trick to getting DWR working is intercepting its initialization javascript. As it turns out, DWR unofficially supports this, but does not document it. Assuming you’re using jQuery, adding this javascript to either an external .js file, or directly in the page, should do the trick:&lt;/p&gt;

    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jQuery(document).ready(function() {
    if (typeof(PTPortalPage)!=&quot;undefined&quot;) {
        //TODO: this check won't work if JS in gateway
        dwr.engine._urlRewriteHandler = doInterceptUrl;
    } else if (document.getElementById(&quot;gatewaybase&quot;) != null) {
        dwr.engine._urlRewriteHandler = doInterceptUrl;
    }
});
    
function doInterceptUrl(data) {
    // this function intercepts http requests from DWR
    // and gateways them using an anchor on the main page
    //TODO: is there a better way? AJAX request for base?
    var rooturl = document.getElementById(&quot;gatewaybase&quot;).href;
    var nongateroot = document.getElementById(&quot;gatewaybase&quot;).target;
    
    data = data.replace(nongateroot, rooturl);
    return data; 
};
&lt;/code&gt;&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What this will do is effectively intercept any javascript requests from your page and add a properly gatewayed URL (via that anchor tag you added in step 1) to the HTTP request.&lt;/p&gt;

&lt;p&gt;Also note that you may need to modify your web.xml with the following init-param for DWR:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;servlet&amp;amp;gt;
  servlet-name&amp;amp;gt;dwr-invokerservlet-name&amp;amp;gt;
  display-name&amp;amp;gt;DWR Servletdisplay-name&amp;amp;gt;
  servlet-class&amp;amp;gt;org.directwebremoting.servlet.DwrServletservlet-class&amp;amp;gt;
  init-param&amp;amp;gt;
     param-name&amp;amp;gt;debugparam-name&amp;amp;gt;
     param-value&amp;amp;gt;trueparam-value&amp;amp;gt;
  init-param&amp;amp;gt;
  
  init-param&amp;amp;gt;
     param-name&amp;amp;gt;crossDomainSessionSecurityparam-name&amp;amp;gt;
     param-value&amp;amp;gt;falseparam-value&amp;amp;gt;
  init-param&amp;amp;gt;
servlet&amp;amp;gt; I'm sure there are probably a million ways to build a better mousetrap when displaying tables with Java, but using the above technologies was quick, easy and rewarding.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Running a Game Server on Amazon EC2</title>
   <link href="http://blog.hross.net/ec2/2009/05/10/ec2gameserver.html"/>
   <updated>2009-05-10T00:00:00+00:00</updated>
   <id>http://blog.hross.net/ec2/2009/05/10/ec2gameserver</id>
   <content type="html">&lt;p&gt;Yes, it’s true that I haven’t posted in quite a while. My bad. Hopefully you enjoy this little tidbit, even though it’s my first non-ALUI post on this blog…&lt;/p&gt;

&lt;h3 id=&quot;game-night&quot;&gt;Game Night&lt;/h3&gt;

&lt;p&gt;Recently, after a long workday some co-workers and friends of mine started discussing a “game night”. All of us have jobs, lives outside of work, and are no longer college students, but all of us remember the glory days of &lt;a href=&quot;http://en.wikipedia.org/wiki/Counter-Strike&quot;&gt;Counterstrike&lt;/a&gt;, &lt;a href=&quot;http://en.wikipedia.org/wiki/Quake&quot;&gt;Quake&lt;/a&gt; and the like.&lt;/p&gt;

&lt;p&gt;Of course, none of us has anything more than a decently performing laptop, and all of us have an aversion to spending money. And so it was that we happened upon a game called &lt;a href=&quot;http://projectorigin.warnerbros.com/fearcombat/main&quot;&gt;F.E.A.R. Combat&lt;/a&gt;. Perhaps its a bit long in the tooth, and perhaps it is behind the times, but it sure is fun, and it sure is &lt;strong&gt;FREE&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The point of that longwinded story is that every other Wednesday has become game night, or more specifically, &lt;strong&gt;F.E.A.R.&lt;/strong&gt; night. And since we are all computer geeks, and we all work in the web technology world in some way or another, &lt;a href=&quot;http://techopener.com/blog/&quot;&gt;someone&lt;/a&gt; brought up the idea of running a &lt;strong&gt;F.E.A.R.&lt;/strong&gt; instance on Amazon’s EC2.&lt;/p&gt;

&lt;p&gt;Recently, I had a bit of time on my hands and an urge to try it out, and thus this post was born…&lt;/p&gt;

&lt;h3 id=&quot;starting-and-connecting-to-an-ec2-instance&quot;&gt;Starting and Connecting to an EC2 Instance&lt;/h3&gt;

&lt;p&gt;First, I signed up for &lt;a href=&quot;http://aws.amazon.com/ec2/&quot;&gt;Amazon EC2&lt;/a&gt; (actually, I had already signed up when I wrote &lt;a href=&quot;http://hross.net/blog/2008/04/integrating-amazon-s3-with-the.html&quot;&gt;this blog post&lt;/a&gt;). The invaluable &lt;a href=&quot;http://docs.amazonwebservices.com/AWSEC2/2008-05-05/GettingStartedGuide/index.html&quot;&gt;Getting Started Guide&lt;/a&gt; contained all the basics I needed to start instances, make images, sign up, etc..&lt;/p&gt;

&lt;p&gt;Next, I made sure to download the &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=609&quot;&gt;ElasticFox plugin for Firefox&lt;/a&gt;. This makes managing and running EC2 instances much easier. If you want to get started quickly, here is a great &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1797&quot;&gt;Getting Started Guide&lt;/a&gt; for the plugin.&lt;/p&gt;

&lt;p&gt;After installing and setting up ElasticFox, I was ready to start up a base image. I chose to run an Ubuntu image, since package management and documentation is readily available. &lt;a href=&quot;http://alestic.com/&quot;&gt;This site&lt;/a&gt; has a few base AMI’s which I used to get started. I simply searched for the AMI ID I wanted and followed the Elasticfox instructions on starting an instance.&lt;/p&gt;

&lt;p&gt;One thing I had to keep in mind was that I wanted to allow the proper TCP/UDP access so that people could connect to my server. In this case, I allowed the following ports:&lt;/p&gt;

&lt;table&gt;
	&lt;tr&gt;&lt;td&gt;Application&lt;/td&gt;&lt;td&gt;Protocol&lt;/td&gt;&lt;td&gt;Port&lt;/td&gt;&lt;/tr&gt;
	
	&lt;tr&gt;&lt;td&gt;SSH&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;22&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;HTTP&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;80&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;F.E.A.R.&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;27888&lt;/td&gt;&lt;/tr&gt;
	&lt;tr&gt;&lt;td&gt;TeamSpeak&lt;/td&gt;&lt;td&gt;UDP&lt;/td&gt;&lt;td&gt;8767&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;The other thing I did, in order to keep things simple for future connections, was associate a static IP with my running instance (these are called &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1346&quot;&gt;Elastic IP’s&lt;/a&gt; in EC2 parlance). The procedure is mind-numbingly simple in ElasticFox, so I’ll refer you to the &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1797&quot;&gt;Getting Started Guide&lt;/a&gt; if you need more information on how to do it.&lt;/p&gt;

&lt;p&gt;At first, I had some issues actually connecting to my image using SSH (Elasticfox will auto launch an SSH client). The problem ended up being that I was using &lt;a href=&quot;http://www.chiark.greenend.org.uk/~sgtatham/putty/&quot;&gt;Putty&lt;/a&gt; for SSH and it does not recognize the private key format used by EC2. Doh. Fortunately, you can convert your keys using Puttygen. Amazon was nice enough to dedicate an appendix in their &lt;a href=&quot;http://docs.amazonwebservices.com/AWSEC2/2008-05-05/GettingStartedGuide/index.html&quot;&gt;Getting Started Guide&lt;/a&gt; for &lt;a href=&quot;http://docs.amazonwebservices.com/AWSEC2/2008-05-05/GettingStartedGuide/index.html?putty.html&quot;&gt;this exact problem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Problem solved.&lt;/p&gt;

&lt;p&gt;My next steps were the steps you’d take to install and configure any server so that it could host F.E.A.R. Combat, a TeamSpeak server (an in-game voice communication server), and a &lt;a href=&quot;http://munin.projects.linpro.no/&quot;&gt;Munin&lt;/a&gt; monitoring instance (so I could get some stats to see how well EC2 performed in a real world scenario).&lt;/p&gt;

&lt;h3 id=&quot;preparing-for-the-installation&quot;&gt;Preparing for the Installation&lt;/h3&gt;

&lt;p&gt;After everything was running, I wanted to make sure I had the prerequisites to run F.E.A.R. and install any optional components. As it turned out, my Ubuntu instance was fairly locked down. In order to download/install what I needed, I had to &lt;a href=&quot;https://help.ubuntu.com/community/Repositories/CommandLine&quot;&gt;update my sources list to included the multiverse and universe repositories&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once this was done, I updated the list of installable applications via:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apt-get update
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;And installed some C++ compatibility libraries for the dedicated server via:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apt-get install libstdc++5
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;At this point I was all set to install the base components of my server.&lt;/p&gt;

&lt;h3 id=&quot;installing-and-configuring-fear-combat&quot;&gt;Installing and Configuring F.E.A.R. Combat&lt;/h3&gt;

&lt;p&gt;My first step was to download the &lt;a href=&quot;http://fear.filefront.com/file/FEAR_v108_Dedicated_Linux_Server;71429&quot;&gt;F.E.A.R. dedicated linux server here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since I already had the prerequisites installed (see above), all I had to do was extract the archive to disk, modify the included &lt;em&gt;start.sh&lt;/em&gt; to my liking (I used a custom configuration via the “optionsfile argument, used nohup to prevent it from shutting down accidentally, etc), and start the server.&lt;/p&gt;

&lt;h3 id=&quot;installing-and-configuring-teamspeak&quot;&gt;Installing and Configuring TeamSpeak&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://www.teamspeak.com/&quot;&gt;TeamSpeak&lt;/a&gt; is an in-game voice communication server. Since my game night buddies are mostly remote, I figured it would be nice to provide some voice communication for trash talk and strategy.&lt;/p&gt;

&lt;p&gt;I logged in as root and ran:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apt-get install teamspeak-server
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;A teamspeak user was added, the server started and I was ready to rock and roll. As for configuring the server… it seemed to work okay, so I didn’t bother =). However, you can find some &lt;a href=&quot;http://forum.teamspeak.com/showthread.php?t=15438&quot;&gt;instructions for configuration here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;installing-and-configuring-munin&quot;&gt;Installing and Configuring Munin&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://munin.projects.linpro.no/&quot;&gt;Munin&lt;/a&gt; is a monitoring tool that allows you to capture CPU, memory, process data, and all kinds of other stats in 5 minute increments. It can be used for monitoring many systems with many kinds of statistics, but that is outside of the scope of this post. For now let’s just say I wanted a simple way to capture statistics for my AMI.&lt;/p&gt;

&lt;p&gt;The installation also turned out to be very simple. It involved using apt-get to install apache and Munin. Rather than regale you with the details, I’ll just point you to &lt;a href=&quot;http://www.debuntu.org/book/export/html/134&quot;&gt;this simple tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I did have some issues getting Munin to work at first, but once I made sure my local node was listening on the loopback adapter only, it seemed to work. See &lt;em&gt;Section 1.3 (Configuring the Node)&lt;/em&gt; of the &lt;a href=&quot;http://www.debuntu.org/book/export/html/134&quot;&gt;tutorial&lt;/a&gt; for details.&lt;/p&gt;

&lt;h3 id=&quot;creating-and-registering-an-ami&quot;&gt;Creating and Registering an AMI&lt;/h3&gt;

&lt;p&gt;At this point I had everything I needed to run a game server. I tested client connections to my Teamspeak host, Apache server hosting Munin, and the F.E.A.R. server itself and everything worked great.&lt;/p&gt;

&lt;p&gt;The only problem was that if I ever shut down the running instance, all of my work would be gone and I would have to re-install everything the next time I wanted to host a game. Thus, I needed to create an AMI from my base image.&lt;/p&gt;

&lt;p&gt;The procedure for this was relatively simple, and well documented in the Getting Started Guide &lt;a href=&quot;http://docs.amazonwebservices.com/AWSEC2/2008-05-05/GettingStartedGuide/index.html?creating-an-image.html&quot;&gt;here&lt;/a&gt;. However, there are a few things you might want to know before you dive in:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You’ll need at least a basic working knowledge of &lt;a href=&quot;http://aws.amazon.com/s3/&quot;&gt;Amazon S3&lt;/a&gt;, since you’ll need it to store your finished AMI. I suggest grabbing &lt;a href=&quot;http://www.s3fox.net/Default.aspx&quot;&gt;S3Fox&lt;/a&gt; and using it to create an Amazon S3 bucket. This process is fairly simple, but still a minor annoyance.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The base image I used did not have the &lt;a href=&quot;http://developer.amazonwebservices.com/connect/entry.jspa?externalID=351&quot;&gt;EC2 API tools&lt;/a&gt; installed on it, which meant that I could not register my EC2 instance without installing them. I did this by running:&lt;/p&gt;

    &lt;p&gt;apt-get install ec2-api-tools&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, all I needed to do was set my &lt;strong&gt;JAVA_HOME&lt;/strong&gt; environment variable and follow the rest of the &lt;a href=&quot;http://docs.amazonwebservices.com/AWSEC2/2008-05-05/GettingStartedGuide/index.html?creating-an-image.html&quot;&gt;Getting Started Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;

&lt;h4 id=&quot;security&quot;&gt;Security&lt;/h4&gt;

&lt;p&gt;As you probably noticed, the configuration on my AMI is hardly secure. I ran things as root, didn’t bother changing passwords or restricting IP’s, etc, etc.. I offer no excuses, save my own laziness.&lt;/p&gt;

&lt;p&gt;However, the nice thing about an AMI is that it is only going to be used on game night for a few hours. I’m hardly worried about being hacked. Any time there is a problem, all I have to do is terminate the instance and boot up another AMI. Since nothing is persistent, and there are no credentials on the box, this is great.&lt;/p&gt;

&lt;p&gt;Imagine if I had set up a dedicated server for this. I’d have to worry about all kinds of hardening due to the longevity of the configuration. Yuck.&lt;/p&gt;

&lt;h4 id=&quot;going-further&quot;&gt;Going Further&lt;/h4&gt;

&lt;p&gt;Of course, as always, there are some things I could have done that would have taken this post further:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Capturing “real” usage stats and anecdotal performance data (is this a feasible, reliable, and cost effective solution?). This will probably follow in a future blog post (after the next “game night”).&lt;/li&gt;
  &lt;li&gt;Writing a wrapper for the AMI so that it can be started and stopped on-demand via the web. Someone could definitely write a dedicated hosting web site if they could figure out all the possible licensing restrictions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise… that’s about it. After reading this you should be in a position to create your own AMI’s using EC2. The overall experience for me was rather pleasant, though there were some things I think Amazon could have done to simplify the process.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Adventures in Search - Part 4 - Search Node Operation</title>
   <link href="http://blog.hross.net/webcenter/2008/12/08/searchpart4.html"/>
   <updated>2008-12-08T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/12/08/searchpart4</id>
   <content type="html">&lt;p&gt;You know, it’s funny how some things can seem extremely complicated and then when you crack them open they turn out to be fairly easy to understand. Remember the mystery behind how a G.I. Joe stayed together, but then you broke one and found it was simply a rubber band holding his guts together? Turns out search server is much like that. A terribly complicated-seeming C program that, fundamentally, is held together by a rubber band.&lt;/p&gt;

&lt;h3 id=&quot;what-is-a-search-node&quot;&gt;What is a Search Node?&lt;/h3&gt;

&lt;p&gt;From my &lt;a href=&quot;http://hross.net/blog/2008/07/adventures-in-search-part-1-wh.html&quot;&gt;previous posts&lt;/a&gt;, you’ve probably inferred that search nodes are the fundamental building blocks of ALUI’s search capability. In fact, search nodes are actually the *only* building blocks of the search capability. Everything you need to set up a clustered or non-clustered search environment is contained in one simple install, a few directories and an executable.&lt;/p&gt;

&lt;p&gt;All this seemingly complicated system amounts to is the following breakdown:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;An executable running somewhere listening for requests&lt;/li&gt;
  &lt;li&gt;An open TCP port that receives text based search queries&lt;/li&gt;
  &lt;li&gt;Two directories that contain everything search needs to operate: a cluster directory and a node directory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s a more complicated picture of what I just listed:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/search_node_architecture.jpg&quot; alt=&quot;search_node_architecture&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 1 - Search Node Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;search-requests&quot;&gt;Search Requests&lt;/h3&gt;

&lt;p&gt;Let’s start with the executable. When you start it up using the command line (from the bin directory in a *nix environment, or via a service on Windows), it uses environment variables to find its various configuration files, starts up a process, opens a TCP socket on whatever port you tell it to, and sits around waiting for stuff to happen.&lt;/p&gt;

&lt;p&gt;The “stuff that happens” turns out to also be fairly simple. Search server doesn’t actually know anything about portals, documents or anything else for that matter. It sits around and waits for one of two things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;An index request (put some information into the search index so it can be searched for later)&lt;/li&gt;
  &lt;li&gt;A search request (search for something in the current index)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two things are specified in a text-based custom language over a TCP port. What I mean is that you, Joe Six-pack, could open up a telnet session to your search server port and type a search query (index or request) freehand, were you so inclined. You would type something like the following:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;( FIELDALIAS ptsearch,[2]PT1,[2]PT1_en,[0.1]PT2,[0.1]PT2_en,[0.1]PT50 ) (((ptsearch:a) TAG phraseQ OR (ptsearch:a*) TAG nearQ) AND ((subtype:&quot;PTCARD&quot;)[0])) AND ((((@type:&quot;PTPORTAL&quot;)[0]) OR ((@type:&quot;PTCONTENTTEMPLATE&quot;)[0])) AND (((ptacl:&quot;u2&quot;) OR (ptacl:&quot;51&quot;))[0]) AND (((ptfacl:&quot;u2&quot;) OR (ptfacl:&quot;51&quot;))[0])) 

METRIC logtf [1] RESULTS 10 PRINT FIELDS parentids,ptacl,ptfacl,PT51,PT56,@type,subtype,ancestors,PT58,PT7,PT53,abstracttype,

PT1,PT1_en,PT2,PT2_en,PT3,PT4,PT5,PT6,PT8,collab_properties,collab_project_url,collab_project_name,collab_icon_alttext_index,collab_acl,publisheduser,portletid TERMS 10000 results[1-10] KWIC 15
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Obviously, this kind of a query isn’t very pretty or intuitive, but the point is you could type it via telnet and search server would spit out an XML formatted response to your query. You can see these types of queries in your search node logs if you set your logging levels high enough. Lucky for you, the search API takes care of all of this heavy lifting and converts those XML results into the pretty HTML you see when you perform a search in the portal.&lt;/p&gt;

&lt;h3 id=&quot;building-a-search-index&quot;&gt;Building a Search Index&lt;/h3&gt;

&lt;p&gt;“Okay Ross,” you’re probably thinking, “I can run search queries over telnet to see what’s in my search index. That’s all well and good, but how does all that junk get in the index in the first place?”&lt;/p&gt;

&lt;p&gt;How indeed. As I mentioned above, that junk gets in there via an index request, which is much like a search request (runs over a TCP port, follows a specific querying language), but allows whoever or whatever to put information into search instead of extract it.&lt;/p&gt;

&lt;p&gt;If you look closely at your Publisher &lt;em&gt;content.properties&lt;/em&gt; file, Collaboration &lt;em&gt;config.xml&lt;/em&gt; file or even at the portal database (PTSERVERCONFIG table), you will see an “Indexing Search Port” and “Indexing Search Host” specified. What these values really do is tell each product (Portal, Publisher, Collaboration) where to submit their new document data (i.e. when someone publishes something, uploads something to a project, or a crawler runs). That data is submitted over the same TCP port to the same type of node that handles queries.&lt;/p&gt;

&lt;h3 id=&quot;how-an-indexing-request-works&quot;&gt;How an Indexing Request Works&lt;/h3&gt;

&lt;p&gt;Here’s a brief explanation followed by a couple of pictures:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;An index request is submitted to a search node. Since that search node may be part of a multi-node cluster, the request goes straight to the cluster file system (remember, all nodes share this directory).&lt;/li&gt;
  &lt;li&gt;The request is assigned a transaction ID and added to a queue on the cluster (you can see this in the form of the requests folder in the cluster folder of your search node).&lt;/li&gt;
  &lt;li&gt;Every search node in the cluster independently maintains its own transaction ID, which corresponds to the last index request it processed. These nodes continually poll the shared requests folder. If they find a transaction that has a higher ID than the one they maintain, they pull the information for that transaction and add it to their local search index. They then update their local transaction ID to match the transaction they just processed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can actually see this process in real time by amping up your search logs and watching the transaction ID’s increment when you upload a collab document, create and admin object, etc.. Here’s a few Powerpoint diagrams I created of this process:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/index_request1.jpg&quot; alt=&quot;index_request1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 2 - Adding an index request to the cluster’s transaction queue.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/index_request2.jpg&quot; alt=&quot;index_request2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 3 - Updating a local search index from the transaction queue.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;As far as node operation goes, that should clear up most of the mystery. At this point, you should understand most of the how’s and why’s of search operation. The last piece to this puzzle is the “checkpoint” feature, which I’ll review in the final exciting chapter of this blog series.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Using diff to avoid re-importing PTE's</title>
   <link href="http://blog.hross.net/webcenter/2008/10/23/ptediff.html"/>
   <updated>2008-10-23T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/10/23/ptediff</id>
   <content type="html">&lt;p&gt;Let’s take a quick timeout from Search for a more basic post…&lt;/p&gt;

&lt;p&gt;I don’t have a “Cool Tools” section of my blog, like some other &lt;a href=&quot;http://function1.com&quot;&gt;notable ALUI bloggers&lt;/a&gt;, but I do know of a few “cool tools” that have helped me do my job. One of my favorites is a fancy &lt;a href=&quot;http://en.wikipedia.org/wiki/Diff&quot;&gt;diff&lt;/a&gt; utility called &lt;a href=&quot;http://www.winmerge.org/&quot;&gt;WinMerge&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(go download it now if you haven’t already)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the primary things I use it for is validating product upgrades. If you’re as lazy and/or paranoid as I am, you have probably given pause during an ALUI upgrade when you saw the step “re-import the PTE”. As most of us know, re-importing a PTE is a mixed bag, as it comes along with a lot of dependencies and can frequently wipe out customizations to web services, portlets, etc. Worse yet, you never quite know what’s happening when you import.&lt;/p&gt;

&lt;p&gt;What if we could analyze a PTE and figure out what changes were made so that we could either:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;make the changes ourselves&lt;/li&gt;
  &lt;li&gt;not bother re-importing&lt;/li&gt;
  &lt;li&gt;at least know what changes were going to be made to our existing data?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Turns out this is rather simple (and, obviously, involves WinMerge).&lt;/p&gt;

&lt;p&gt;Let’s use a relevant example to demonstrate: a Publisher upgrade from 6.4 to 6.5. This is an upgrade of a minor revision number, so you would think there would be relatively few changes to the PTE’s. Nonetheless, the install guide tells me to re-import, re-import, re-import.&lt;/p&gt;

&lt;p&gt;Yuck.&lt;/p&gt;

&lt;p&gt;Instead, I’ll take an alternate approach. First, I run the Publisher 6.5 upgrade installer as I normally would. However, once I get to the re-import step, I navigate to the ptcs/6.4/serverpackages directory of my &lt;strong&gt;previous Publisher install&lt;/strong&gt; and grab the publisher.pte file therein. Next, I grab the same PTE file from my ptcs/6.5/serverpackages directory.&lt;/p&gt;

&lt;p&gt;Now I have both default install PTE’s. Any differences between them will be the changes due to the 6.4 to 6.5 upgrade. Since these PTE’s are really just XML files with fairly obvious naming conventions, I simply open them up side by side in WinMerge and compare the differences…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/diffpte.jpg&quot; alt=&quot;pte_diff&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As it turns out, the only changes to the Publisher package in 6.5 are some /jspell URL’s that have been added to the gateway settings for some web services. Since I can read the new URL in WinDiff, I can copy the gateway URL’s and add them manually. Now I no longer need to import the PTE.&lt;/p&gt;

&lt;p&gt;… and even if there were more changes and I had to re-import, I would be well informed of what they were before running the import.&lt;/p&gt;

&lt;p&gt;Okay. We now return you to your regularly scheduled programming.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Adventures in Search - Part 3 - Search Administration</title>
   <link href="http://blog.hross.net/webcenter/2008/10/20/searchpart3.html"/>
   <updated>2008-10-20T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/10/20/searchpart3</id>
   <content type="html">&lt;p&gt;Here we are, back again for another installment in my new blog “mini-series” about search. When I first started researching these posts (er… presentation, actually) the mini-series might have been more aptly titled “Lost” (not to be confused with ABC’s hit series, except for the mass confusion and never ending storyline).&lt;/p&gt;

&lt;p&gt;Last time I promised some hard-hitting dirt on Search Administration, and as always, I deliver on my blog promises. Okay, maybe hard hitting is a bit of a stretch… let’s talk about Search Administration. Most of you are probably familiar with the &lt;em&gt;Search Cluster Manager&lt;/em&gt; and &lt;em&gt;Search Service Manager&lt;/em&gt; in the Administrative Utilities drop down, but what are they and how do they work?&lt;/p&gt;

&lt;p&gt;Let’s start tackling this with a diagram:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/search_admin_1.jpg&quot; alt=&quot;search_admin_1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This diagram represents the end-all be-all of the search administration process. There are two parts:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Portal communication with a search node directly. This is the *Search Service Manager *(left side of the diagram). It is basically the portal asking the node about the health and topology of the search server and the node replying with this information. This node is extremely important, since it tells the portal front end how and which search nodes to query. The query is performed over the same port as any other search request, using the same mechanisms, and will show up in your search logs if you have them at a high enough verbosity.&lt;/li&gt;
  &lt;li&gt;Portal communication with the search topology indirectly. This is done via the *Search Cluster Manager *(right side of the diagram). I have heard much rumor and hearsay regarding the Search Cluster Manager, so let me clear up any misconceptions you might have with a properly bolded and formatted statement:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The Search Cluster Manager is a Java web application that reads and writes files on the Cluster File System.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What this really means is that the Search Cluster Manager is totally unnecessary. All administration can be done with the &lt;strong&gt;cadmin&lt;/strong&gt; tool (in your search server’s bin directory) or via direct changes to specific initialization files (this is what the Search Cluster Manager does, anyway). So basically, the diagram above actually looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/search_admin_2.jpg&quot; alt=&quot;search_admin_2&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;wrap-up&quot;&gt;Wrap Up&lt;/h3&gt;

&lt;p&gt;So that’s it. Basically, the take-away’s here are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Search Cluster Manager is simply a prettied up version of the command line utility and does not need to run for search to function in the portal.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Search Service Manager controls the contact node and determines search topology for the portal front end.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pretty simple, eh? Next up… some more interesting details on node operation.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Adventures in Search - Part 2 - Search Architecture</title>
   <link href="http://blog.hross.net/webcenter/2008/10/15/searchpart2.html"/>
   <updated>2008-10-15T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/10/15/searchpart2</id>
   <content type="html">&lt;h3 id=&quot;breaking-down-a-search-collection&quot;&gt;Breaking Down a Search Collection&lt;/h3&gt;

&lt;p&gt;Last time I listed the various functions of search and reposted my first search slide. It was fairly simple, just an abstract “Search Collection” diagram. This time let’s break that diagram down a bit more:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/what_is_search.jpg&quot; alt=&quot;what_is_search_3&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What we see above is a less abstract view of the same diagram. Instead of one giant “Search” lump, we actually have an API, which makes the communication decisions, and a collection of search nodes. These nodes are just processes running somewhere, listening on a specific port. More about them later.&lt;/p&gt;

&lt;h3 id=&quot;partitions&quot;&gt;Partitions&lt;/h3&gt;

&lt;p&gt;That was pretty simple, right? Let’s throw in one more wrinkle before moving on to the complicated bits: &lt;em&gt;Partitions&lt;/em&gt;. A partition is simply a grouping of search data into a set of nodes. Applying that concept to the above diagram, a partitioning of our search collection might look something like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/what_is_search_partitions.jpg&quot; alt=&quot;what_is_search_partitions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In other words, some of the data indexed by search (search results) will reside in Partition 1 on Node 1, and some of the data will reside in Partition 2 on Nodes 1 and 2. If we draw out the partitions in a more abstract manner, they look like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/what_is_search_partitions_abstract.jpg&quot; alt=&quot;what_is_search_partitions_abstract&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As you can see, there are two separate “bins” of data. When new information is indexed it goes into one of these two bins. It is important to note that neither partition contains duplicate data, so when you search for something the results from Partition 1 and Partition 2 must be &lt;strong&gt;aggregated&lt;/strong&gt; together. Duplicate data will, however, exist on Nodes 1 and 2 in Partition 2 (see above).&lt;/p&gt;

&lt;h3 id=&quot;search-coordination&quot;&gt;Search Coordination&lt;/h3&gt;

&lt;p&gt;With all this data moving about, being partitioned, searched, etc, you may be wondering how all of the search nodes communicate with one another. How do they know which partition they belong to, which node they are and what data has already been indexed?&lt;/p&gt;

&lt;p&gt;The answer, it turns out, is extremely simple. They all must share at least one common set of files and directories, which I’ll call the “Cluster File System”. There is no special port-to-port communication, magic pixie dust, or any other way for search nodes to talk to each other. The cluster file system contains configuration information about the entire search topology, as well as a common queue/locking mechanism for incoming search indexing requests (more detail later). In other words, our previous diagram now looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/what_is_search_cluster_file_system.jpg&quot; alt=&quot;what_is_search_cluster_file_system&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And that’s really all there is to it. I’ve just covered all of the concepts you’ll need for a basic understanding of search.&lt;/p&gt;

&lt;h3 id=&quot;wrap-up&quot;&gt;Wrap Up&lt;/h3&gt;

&lt;p&gt;Alright, well we’ve covered the basics, but as you know, I’m never fully satisfied with the basics. Hopefully you now have a base understanding of search operation and are ready to stick with me for the under-the-covers part. Most of the information I’ve provided to this point is covered in the docs, just (in my opinion) not very well. Next time look for some more detailed information on how search administration works and under the covers node operation.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Adventures In Search - Part 1 - What is Search?</title>
   <link href="http://blog.hross.net/webcenter/2008/07/22/searchpart1.html"/>
   <updated>2008-07-22T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/07/22/searchpart1</id>
   <content type="html">&lt;p&gt;If you’ll recall, a few posts ago I promised to start fleshing out the presentation I gave at Participate in this blog. It’s a somewhat boring task, since I already came up with a presentation, but since I gave the presentation and posted it to my blog, there has been a lot more interest in it than I anticipated.&lt;/p&gt;

&lt;p&gt;Apparently, everybody else is just as confused about what search is and how it works as I was. So how about we break out the flashlight and provide a point of reference for the folks who weren’t at Participate, or who prefer reference material to a presentation (I know I am in that camp).&lt;/p&gt;

&lt;h3 id=&quot;what-is-search&quot;&gt;What is Search?&lt;/h3&gt;

&lt;p&gt;As illustrated by the diagram below, when I talk about search, I simply mean a repository of information. On one side, information about the stuff we want to search is added to the repository and on the other side users or programs query that repository with requests for that information:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/what_is_search_basic.jpg&quot; alt=&quot;what_is_search&quot; /&gt;&lt;/p&gt;

&lt;p&gt;However, this is a somewhat simplified version of Search, since it interfaces with our portal in more ways than just the search box in the header.&lt;/p&gt;

&lt;p&gt;Here is a comprehensive listing of search uses (that I know of):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Portal Search Box&lt;/strong&gt; - When you search for things in the portal as a non-administrator.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Administrative Search&lt;/strong&gt; - When you search for things as an administrator (folders, objects, etc)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Knowledge Directory&lt;/strong&gt; - All of the folder/document browsing screens are built from the search index, not the database. You can change this in Portal Admin Options, but it’s not recommended, for performance reasons.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Content Crawlers&lt;/strong&gt; - Every time a new document is submitted, metadata is updated, etc (basically every time a crawler is run)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Publisher&lt;/strong&gt; - Used only when publishing/saving content. Publisher search is actually just a database query)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Collaboration File Upload&lt;/strong&gt; - Used when uploading/indexing Collaboration documents.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Collaboration Search&lt;/strong&gt; - Collaboration search actually does use Search.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;IDK Search Factory&lt;/strong&gt; - When you use the IDK to perform search requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s really it. In its basic operation, search is extremely simple. Next time I’ll start to delve into search architecture, specifically Nodes and Partitions.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Adventures In Search - Part 1 - What is Search?</title>
   <link href="http://blog.hross.net/webcenter/2008/07/01/authsources.html"/>
   <updated>2008-07-01T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/07/01/authsources</id>
   <content type="html">&lt;p&gt;Since &lt;a href=&quot;http://fsanglier.blogspot.com/&quot;&gt;Fabien finally updated his blog&lt;/a&gt; with a nice write up on the &lt;a href=&quot;http://fsanglier.blogspot.com/2008/06/alui-publisher-part-2-increase_30.html&quot;&gt;published content redirect&lt;/a&gt;, and I told him I would try and beat him to the punch, I think I now owe a post or two. This one has been sitting unpublished for a few days, so here we go…&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;One of my favorite parts of portal work is the fact that most portal implementations touch a variety of technologies: different programming languages, a variety of internal systems and many different authorization and authentication mechanisms. One of the most common of these is LDAP, whether it be in the form of Active Directory or some other LDAP server.&lt;/p&gt;

&lt;p&gt;Unfortunately, I have the same problem I’d like to think most techies have: if I don’t work with something for 6 months or so, I tend to forget at least half of the important details. And since LDAP integration usually only happens every so often, mostly on new portal installs, I find I tend to forget the details only to have to re-learn them again.&lt;/p&gt;

&lt;p&gt;Hopefully this post can serve as a reminder, and perhaps a primer for the uninitiated.&lt;/p&gt;

&lt;h3 id=&quot;user-synchronization&quot;&gt;User Synchronization&lt;/h3&gt;

&lt;p&gt;For every user that logs into the portal, the portal has a record of their account in its &lt;strong&gt;PTUSERS&lt;/strong&gt; database table. No matter that they are in Active Directory, LDAP, what have you, the user still must be listed in this table in order to log into the portal (basically meaning they are a user object in the portal).&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;PTUSERS&lt;/strong&gt; table is updated periodically by jobs run on configured authentication sources which essentially go out to an LDAP directory (or custom directory), ask for any new user accounts or groups and set them up in the portal.&lt;/p&gt;

&lt;p&gt;One thing to note about this mechanism is that there is a time lag between when a user is created in a directory and when they can log into the portal. It would be nice if the authentication source were queried if the user was not found, and you can introduce customizations to do this, but OOTB you have to wait on the sync job.&lt;/p&gt;

&lt;p&gt;Every authentication source in the portal also has an associated prefix, as well as a set of users and groups. The prefix is like (and can even be) a Windows domain. It is used to distinguish duplicate user names on different authentication sources.&lt;/p&gt;

&lt;h3 id=&quot;a-quick-reference&quot;&gt;A Quick Reference&lt;/h3&gt;

&lt;p&gt;Unfortunately, it can be highly confusing when you’re trying to figure out what all the different user properties in the portal are, how they relate to LDAP/AD configuration, and what they actually mean. To that end, I have come up with the following table that (hopefully) explains each mapping in enough detail that you can see how it is built and what it is meant to do.&lt;/p&gt;

&lt;table&gt;
	&lt;tr&gt;
	&lt;td&gt;PTUSERS Column&lt;/td&gt;
	&lt;td&gt;AD Auth Source Value&lt;/td&gt;
	&lt;td&gt;LDAP Auth Source Value&lt;/td&gt;
	&lt;td&gt;User Profile Value&lt;/td&gt;
	&lt;td&gt;Description&lt;/td&gt;
	&lt;/tr&gt;
	
	&lt;tr&gt;
	&lt;td&gt;NAME&lt;/td&gt;
	&lt;td&gt;Auth Source Prefix + User Name Attribute&lt;/td&gt;
	&lt;td&gt;Auth Source Prefix + User Name Attribute&lt;/td&gt;
	&lt;td&gt;Display Name&lt;/td&gt;
	&lt;td&gt;A &amp;quot;throw away&amp;quot; descriptive name for the user. Can be changed with a PWS or manually by the user.&lt;/td&gt;
	&lt;/tr&gt;
	
	&lt;tr&gt;
	&lt;td&gt;MAPPINGAUTHNAME&lt;/td&gt;
	&lt;td&gt;User Name Attribute&lt;/td&gt;
	&lt;td&gt;User Name Attribute&lt;/td&gt;
	&lt;td&gt;none&lt;/td&gt;
	&lt;td&gt;A base mapping name for the user (without auth source prefix)&lt;/td&gt;
	&lt;/tr&gt;
	
	&lt;tr&gt;
	&lt;td&gt;LOGINNAME&lt;/td&gt;
	&lt;td&gt;Auth Source Prefix + User Name Attribute&lt;/td&gt;
	&lt;td&gt;Auth Source Prefix + User Name Attribute&lt;/td&gt;
	&lt;td&gt;Login Name&lt;/td&gt;
	&lt;td&gt;The name a user has to type to log into the portal on the login screen (including the value that must be in the auth source drop down)&lt;/td&gt;
	&lt;/tr&gt;
	
	&lt;tr&gt;
	&lt;td&gt;AUTHUNIQUENAME&lt;/td&gt;
	&lt;td&gt;objectGUID in AD (not specifiable)&lt;/td&gt;
	&lt;td&gt;User Unique Name Attribute (defaults to DN)&lt;/td&gt;
	&lt;td&gt;Remote Unique Name&lt;/td&gt;
	&lt;td&gt;A uniqueness constraint in the directory to tell the portal this is the same user, even if their login or name changes.&lt;/td&gt;
	&lt;/tr&gt;
	
	&lt;tr&gt;
	&lt;td&gt;AUTHUSERNAME&lt;/td&gt;
	&lt;td&gt;User Authentication Attribute (usually userPrincipalName to guarantee cross domain uniqueness - not sAMAccountName which is only unique in the domain)&lt;/td&gt;
	&lt;td&gt;User Authentication Name Attribute (if not specified, defaults to DN - Distinguished Name)&lt;/td&gt;
	&lt;td&gt;Remote Authentication Name&lt;/td&gt;
	&lt;td&gt;The property used to authenticate the user with the directory. May not be used for authentication if an SSO solution is in place.&lt;/td&gt;
	&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;A couple of notes:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The help files on the authentication source configuration files are surprisingly helpful.&lt;/li&gt;
  &lt;li&gt;If you need to figure out your LDAP/AD structure, see user properties or run queries, I highly recommend this free tool: &lt;a href=&quot;http://www.softerra.com/download.htm&quot;&gt;Softerra LDAP Browser&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;See you next time.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Everything You Ever Wanted to Know About Search (But Were Afraid to Ask)</title>
   <link href="http://blog.hross.net/webcenter/2008/05/15/everythingsearch.html"/>
   <updated>2008-05-15T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/05/15/everythingsearch</id>
   <content type="html">&lt;p&gt;As I mentioned previously on this blog, I’m at &lt;a href=&quot;http://participate.bea.com/&quot;&gt;BEA Participate&lt;/a&gt; this week. In order to convince the powers that be to send me out here for free, I gave a presentation on Search Server. As usual, it was based on my initial lack of understanding of the product and subsequent painful discovery of its inner workings.&lt;/p&gt;

&lt;p&gt;If you were here, I hope you enjoyed it. If you weren’t, I’ll try to write up a few detailed posts when I get some time. For now, here’s a &lt;a href=&quot;http://docs.google.com/Presentation?id=dghjj2v6_2chx46wdz&quot;&gt;link to the slide deck&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As for the conference itself, my hat is off to all the BEA folk who pulled it off. It was great to see our customers, partners and BEA employees at the event. It was definitely a reminder of the large number of smart, and fun, people I have the pleasure of working with on a regular basis. And a special kudos to &lt;a href=&quot;http://thebdgway.com/&quot;&gt;BDG&lt;/a&gt;, who pulled together a &lt;a href=&quot;http://participate.bea.com/&quot;&gt;really slick portal for the conference&lt;/a&gt;.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Keeping Track of Your Job Logs</title>
   <link href="http://blog.hross.net/webcenter/2008/04/25/joblogs.html"/>
   <updated>2008-04-25T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/04/25/joblogs</id>
   <content type="html">&lt;p&gt;Over time, there are a lot of bad things that can happen to a portal installed at a customer site. Unfortunately, philosophically speaking, we are all fighting entropy in our daily lives (think about how many times you’ve done laundry or taken out the trash). Portal maintenance is just another way of doing that.&lt;/p&gt;

&lt;p&gt;Every portal instance that’s running properly has at least one Automation Server running in the background. The Automation Server is supposed to take care of automated tasks like Analytics data collation, the periodic synchronization of users and groups, and system maintenance.&lt;/p&gt;

&lt;p&gt;The cool thing about Automation Server is that it will save a log of each job’s results so that you can view it later. These results are stored in the database in the &lt;em&gt;PTJOBLOGS&lt;/em&gt; table. The uncool thing is that sometimes the job results are &lt;strong&gt;many&lt;/strong&gt; pages long. So many pages, in fact, that a few runs can start to eat up space in your database at an enormous rate. There are a few things you should know about this, and a few things you should know about how to mitigate it.&lt;/p&gt;

&lt;h3 id=&quot;managing-job-log-space-usage&quot;&gt;Managing Job Log Space Usage&lt;/h3&gt;

&lt;p&gt;First, let’s review your space-saving options:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;em&gt;You can reduce the frequency of jobs that have verbose output.&lt;/em&gt; This is probably a crappy option, unless you really don’t need to run the job in question.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;You can reduce the verbosity of your jobs.&lt;/em&gt; Every job in the portal contains a setting called &lt;strong&gt;Logging Level&lt;/strong&gt;. This setting allows you to control what actually shows up in the result log for the job. Your options are Silent, Low, Normal and Verbose. Obviously, setting a job to silent can have a detrimental impact if it fails (you won’t know why), whereas setting it to verbose can have a detrimental impact on the amount of space it eats up. Some jobs don’t always need a high level of verbosity.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;You can increase the frequency at which your job logs are cleaned up.&lt;/em&gt; By default, the portal will save job logs for 60 days before they are removed from the database. The* Weekly Housekeeping Job* will remove any job logs older than 60 days each time it is run. In my experience, this is quite a long time. Unfortunately, there is no way to change this value in the user interface of the portal. You can, however, change it in the database with the following SQL:&lt;/p&gt;

    &lt;p&gt;UPDATE PTSERVERCONFIG SET VALUE= WHERE SETTINGID=15&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Where ** is the number of days you’d like to keep your job logs for. Reducing this number will reduce the size of your PTJOBLOGS table. &lt;em&gt;(do I need to mention you should double check the SETTINGID and back up the table before running this?)&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;what-happens-when-the-job-log-table-gets-too-big&quot;&gt;What happens when the job log table gets too big?&lt;/h3&gt;

&lt;p&gt;If you haven’t mitigated the size of your &lt;em&gt;PTJOBLOGS&lt;/em&gt; table, or you somehow forgot to schedule the &lt;em&gt;Weekly Housekeeping Job&lt;/em&gt; for a while, or you have some other space problem in your database, you may run into some issues:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Your &lt;em&gt;Weekly Housekeeping Job&lt;/em&gt; may fail. Unfortunately, this job tries to run a query on PTJOBLOGS and then delete the necessary rows. If you have an enormous number of rows, the SQL statement it uses to do this is a long running operation which may actually cause automation server to think the Weekly Housekeeping Job has become unresponsive and kill it (it will show up as Failed in your job history).&lt;/li&gt;
  &lt;li&gt;Your database may have space issues.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To correct these problems, you can do one of two things. You can either truncate the PTJOBLOGS table (not recommended, but possibly necessary if you’re in dire straits), or you can run something like the following (this is PL/SQL):&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DELETE FROM PTJOBLOGS WHERE INSTANCEID SELECT MAX(INSTANCEID) FROM PTJOBHISTORY WHERE RUNTIME '01/01/2008','mm/dd/yyyy'));
DELETE FROM PTJOBOPHISTORY WHERE RUNTIME '01/01/2008','mm/dd/yyyy');
DELETE FORM PTJOBHISTORY WHERE RUNTIME '01/01/2008','mm/dd/yyyy');
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Note that you’ll have to modify this a bit for it to work on SQL Server, and you will need to change the date as appropriate, but you get the idea.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Using Content Expiration to Improve Portal Performance</title>
   <link href="http://blog.hross.net/webcenter/2008/04/21/contentexpiration.html"/>
   <updated>2008-04-21T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/04/21/contentexpiration</id>
   <content type="html">&lt;p&gt;Recently I was asked by a colleague what kind of tips I might have for portal administrators in order to compile a “top 10” list of portal tips and tricks. I hate to possibly ruin the surprise, in case it makes it to the &lt;a href=&quot;http://www.bea.com/participate/&quot;&gt;Participate&lt;/a&gt; presentation, but one of the tips I often give people is to enable content expiration on their image server.&lt;/p&gt;

&lt;p&gt;The portal has a ton of images and javascript that get provided to a user’s browser on each request, and this is not necessarily a good thing (check out &lt;a href=&quot;http://developer.yahoo.com/yslow/&quot;&gt;Yahoo’s YSlow&lt;/a&gt; analysis of it). Luckily, those images/javascript don’t change very often. Thus, we can normally tell the user’s browser to cache those images so it doesn’t have to ask for them every time.&lt;/p&gt;

&lt;p&gt;Sometimes, especially on intranet portals, we can cache those images for days at a time. This is called configuring content expiration. Although it doesn’t improve “real” performance, it sure reduces the amount of round trips someone’s browser has to make, thereby improving perceived performance. Here’s a few links to give you specifics on configuring it in your image server of choice (courtesy of Google, of course):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://technet2.microsoft.com/windowsserver/en/library/1beefc3b-5117-4812-81a5-f7cf7b1997b71033.mspx&quot;&gt;How to configure content expiration in IIS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://httpd.apache.org/docs/2.0/mod/mod_expires.html&quot;&gt;How to configure content expiration in Apache&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you’re looking for more details on caching/performance improvement, &lt;a href=&quot;http://www.mnot.net/cache_docs/#EXPIRES&quot;&gt;this is an interesting article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Speaking of &lt;a href=&quot;http://www.bea.com/participate/&quot;&gt;Participate&lt;/a&gt;, I’ll be giving a (fairly technical) presentation on Search Server, so be sure to give me a heads up if you’ll be attending.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Integrating Amazon S3 with the Portal</title>
   <link href="http://blog.hross.net/s3/2008/04/10/integratings3.html"/>
   <updated>2008-04-10T00:00:00+00:00</updated>
   <id>http://blog.hross.net/s3/2008/04/10/integratings3</id>
   <content type="html">&lt;p&gt;Once again, my blog posting has been sparse for the past few weeks. But, as the old adage goes: good things come to those who wait. As you can see from the title of this post, good things come in the form of integrating your portal with Amazon’s S3 web service framework. Hopefully you think that’s cool. Otherwise you may as well stop reading right now.&lt;/p&gt;

&lt;p&gt;Okay. For those of you still with me… down the rabbit hole we go…&lt;/p&gt;

&lt;h3 id=&quot;what-is-s3&quot;&gt;What is S3?&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://www.amazon.com/gp/browse.html?node=16427261&quot;&gt;Amazon S3&lt;/a&gt; is a recently released pay-as-you -go “&lt;strong&gt;S&lt;/strong&gt;imple &lt;strong&gt;S&lt;/strong&gt;torage &lt;strong&gt;S&lt;/strong&gt;ervice”. Hence the alliterative S3 moniker. Simply put, rather than worrying about your storage needs by buying more disk, you open an account with Amazon and they provide you with an unlimited REST/SOAP based interface to store as much content as you want. You pay for uploads/downloads and storage space (prices are on the main page, or you can check out &lt;a href=&quot;http://calculator.s3.amazonaws.com/calc5.html&quot;&gt;this calculator&lt;/a&gt;). Relatively speaking, the pay as you go model works great for a rapidly expanding site or for those who don’t want to deal with the maintenance headaches of keeping up their own storage space.&lt;/p&gt;

&lt;p&gt;In terms of the portal, integration with S3 means a place we could store an infinite amount of document data. Ideally, this would be for any of the core ALUI portal services: Knowledge Directory, Collaboration, Publisher, etc.&lt;/p&gt;

&lt;p&gt;But how could we go about accomplishing this…?&lt;/p&gt;

&lt;h3 id=&quot;integration-through-the-document-repository&quot;&gt;Integration Through the Document Repository&lt;/h3&gt;

&lt;p&gt;Ah yes, my old friend the Document Repository. If you’ll recall from previous posts, the repository is just that: a central place for storing document data in the portal. So what if we could somehow create our own repository, or modify the existing one, to upload our documents to Amazon S3 instead of the file system?&lt;/p&gt;

&lt;p&gt;As it turns out, I’m guilty of a little unintentional foreshadowing. If you read my comments on repository configuration in Part 1 of Deconstructing the Document Repository, you’ll see a mention of the possibility of implementing other types of providers.&lt;/p&gt;

&lt;p&gt;And guess what? that’s exactly what I ended up doing.&lt;/p&gt;

&lt;h3 id=&quot;ah-but-its-never-quite-that-simple&quot;&gt;Ah, but it’s never quite that simple…&lt;/h3&gt;

&lt;p&gt;Unfortunately, in the course of writing this article I discovered a bug in the document repository. Apparently nobody’s ever written another provider for it. How do I know that? Well, there’s an explicit cast to the FileSystemProvider in one of the basic classes that enable DR operation (yes, I realize that sentence is entirely technical mumbo jumbo). In order to implement your own provider you have to patch the class to get it to work.&lt;/p&gt;

&lt;p&gt;Hopefully, I can convince the guys in engineering to fix this minor bug, but until then I’ve included a patched version below, along with some install instructions:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Download &lt;a href=&quot;/assets/StartUploadOperation.class&quot;&gt;this file here&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Realize it’s a .class file and think “what the heck are you having me do to my DR, Ross?”&lt;/li&gt;
  &lt;li&gt;Follow these instructions anyway.&lt;/li&gt;
  &lt;li&gt;Unpack your &lt;em&gt;$PT_HOME/ptdr/6.x/webapp/dr.war&lt;/em&gt; file with your favorite zip editor (you’ll be doing this later, anyway).&lt;/li&gt;
  &lt;li&gt;Open &lt;em&gt;WEB-INF/lib/dr.jar&lt;/em&gt; (I have previously recommended &lt;a href=&quot;http://www.rarlab.com/&quot;&gt;WinRAR&lt;/a&gt; for these types of things).&lt;/li&gt;
  &lt;li&gt;Replace the exact same file under &lt;em&gt;complumtreedrtransportglueserver.&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Repack everything, restart and test the document repository as a sanity check. You should be good to go.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;(P.S. – I don’t have original DR source, so this is decompiled and recompiled code. Did I mention this blog should come with a disclaimer?)&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;signing-up-for-s3&quot;&gt;Signing Up For S3&lt;/h3&gt;

&lt;p&gt;The next step in this process is to get yourself an Amazon S3 account. Since it costs money, I’m not going to just go and provide you with mine (nice try though). Simply &lt;a href=&quot;https://aws-portal.amazon.com/gp/aws/developer/subscription/index.html/104-5852112-7419144?ie=UTF8&amp;amp;serviceID=8&amp;amp;offeringId=6&amp;amp;servicePlanID=6&amp;amp;AWS%5Fredirect=true&amp;amp;awscbctx=Amazon%20Simple%20Storage%20Service&amp;amp;serviceName=Amazon%20Simple%20Storage%20Service&amp;amp;awsrid=&amp;amp;AWS%5Fnode=16427261&quot;&gt;sign up here&lt;/a&gt; and note your access key and secret key in the email they send you (you’ll be using them later).&lt;/p&gt;

&lt;p&gt;Now you have two choices… you can continue with me to the &lt;strong&gt;&lt;em&gt;How it Works&lt;/em&gt;&lt;/strong&gt; section, or you can skip right ahead to the &lt;strong&gt;&lt;em&gt;How to Install&lt;/em&gt;&lt;/strong&gt; section.&lt;/p&gt;

&lt;h3 id=&quot;how-it-works&quot;&gt;How it Works&lt;/h3&gt;

&lt;p&gt;A glutton for punishment, eh? Alright. Here we go…&lt;/p&gt;

&lt;h4 id=&quot;managing-s3&quot;&gt;Managing S3&lt;/h4&gt;

&lt;p&gt;First things first. There’s a great &lt;a href=&quot;http://jets3t.s3.amazonaws.com/index.html&quot;&gt;open source Java toolkit&lt;/a&gt; called &lt;a href=&quot;http://jets3t.s3.amazonaws.com/index.html&quot;&gt;JetS3t&lt;/a&gt;. It abstracts all those fun SOAP/REST calls you might otherwise be making and gives you straight up Java objects to play with. I highly recommend using it if you’re planning on any S3/Java development (there are others for .NET, etc). Here’s some links for you to play with if you want to know more:&lt;/p&gt;

&lt;p&gt;Out of the box, JetS3t just works: it uses &lt;em&gt;REST&lt;/em&gt; and *HTTPS *calls only, so security is fairly good. You can reconfigure it by looking at the advanced configuration guide, should you so choose. You can also take a look at the additional jar’s it requires (&lt;a href=&quot;http://commons.apache.org/&quot;&gt;apache commons&lt;/a&gt;) and go from there.&lt;/p&gt;

&lt;p&gt;Should you want to manage your S3 account (and included files) without the benefit of actual programming, I highly recommend you &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/3247&quot;&gt;grab the S3Fox Firefox Plugin&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;creating-a-repository&quot;&gt;Creating a Repository&lt;/h4&gt;

&lt;p&gt;Before I go any further, let me provide you &lt;a href=&quot;/assets/s3provider.zip&quot;&gt;this zip file&lt;/a&gt; of the appropriate jar’s for this project. The only code I’ve written is contained in &lt;em&gt;s3provider.jar&lt;/em&gt;. The source I’m about to explain is also included therein.&lt;/p&gt;

&lt;p&gt;The source itself is fairly simple. The DR allows us to implement a handful of interfaces (one for a document, one for a repository and one for a factory class that creates the repository). After that, all we have to do is manage where our documents go, how they are named, and implement the requisite functions of each class.&lt;/p&gt;

&lt;p&gt;Really, there’s not much to the source. I simply generate a new document when asked by the DR, open output/input streams to new or existing documents using their ID’s, and generate new GUIDs for new documents after they are uploaded. I used a couple of the pre-existing temp file management classes to make my job even easier. All in all, the hardest part was understanding how the interfaces were supposed to work without any documentation.&lt;/p&gt;

&lt;p&gt;The GUID’s I used for unique document naming on upload were generated using the &lt;a href=&quot;http://jug.safehaus.org/&quot;&gt;Java Uuid Generator&lt;/a&gt;, which is an open source native Java implementation that worked quite well for my purposes.&lt;/p&gt;

&lt;p&gt;Perhaps you were expecting more complexity? I was fairly impressed with the DR’s flexible implementation. Actually, I initially started this project by rolling my own “document repository”, but it seemed excessively complicated and I ended up sniffing around the DR source to see if I could do something easier. Turns out I could.&lt;/p&gt;

&lt;h3 id=&quot;how-to-install&quot;&gt;How to Install&lt;/h3&gt;

&lt;p&gt;Got bored of the &lt;strong&gt;&lt;em&gt;How it Works Section&lt;/em&gt;&lt;/strong&gt;, didn’t you?&lt;/p&gt;

&lt;p&gt;This will take a bit of effort, and faith, on your part, but I assure you it’ll be worth it in the long run (&lt;strong&gt;&lt;em&gt;oh yeah, you might want to back up these files before you start messing with them&lt;/em&gt;&lt;/strong&gt;):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;First we need to add the appropriate jars to the war file:
    &lt;ol&gt;
      &lt;li&gt;Unpack your &lt;em&gt;$PT_HOME/ptdr/6.x/webapp/dr.war&lt;/em&gt; file with your favorite zip editor.&lt;/li&gt;
      &lt;li&gt;Extract &lt;a href=&quot;/assets/s3provider.zip&quot;&gt;this zip file&lt;/a&gt; to your hard drive and add the contained jars to the war file’s &lt;em&gt;WEB-INF/lib&lt;/em&gt; directory. See the &lt;strong&gt;&lt;em&gt;How it Works&lt;/em&gt;&lt;/strong&gt; section for details on what these jars do.&lt;/li&gt;
      &lt;li&gt;Re-pack the war file.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Open your &lt;em&gt;$PT_HOME/ptdr/6.1/settings/config/dr-server.xml&lt;/em&gt; file and get ready to start fiddling.&lt;/li&gt;
  &lt;li&gt;Now, set up the provider under your desired application node to amazon: &lt;strong&gt;amazon&lt;/strong&gt;. The cool thing here is that you could register one or all of your document repository services to work with S3. Simply change the entries for ptcollab, ptupload, etc from the file system provider to the S3 provider.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Next, you need to configure the provider. Add a provider to the providers section of the configuration file, like so:&lt;/p&gt;

    &lt;provider&gt;
    &lt;name&gt;amazon&lt;/name&gt;
    &lt;enabled&gt;true&lt;/enabled&gt;
    &lt;factory&gt;com.plumtree.dr.provider.amazon.S3Factory&lt;/factory&gt;
		
    &lt;applications&gt;
        &lt;application&gt;
            &lt;name&gt;ptupload&lt;/name&gt;
            &lt;aws&gt;
                &lt;encrypted&gt;false&lt;/encrypted&gt;
                &lt;awsAccessKey&gt;XXXXXXXXXXXXXXX&lt;/awsAccessKey&gt;
               &lt;awsSecretAccessKey&gt;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&lt;/awsSecretAccessKey&gt;
            &lt;/aws&gt;
        &lt;/application&gt;
    &lt;/applications&gt;
&lt;/provider&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that you will have to add an application section &lt;strong&gt;&lt;em&gt;for each application you changed in step 2&lt;/em&gt;&lt;/strong&gt;. However, configuration is simple enough. You will really only need to change three things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;encrypted&lt;/strong&gt; - Are your AWS access key and secret key encrypted? Obviously in a production environment they should be, but for testing purposes I normally leave them in plain text.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;awsAccessKey&lt;/strong&gt; - your Amazon S3 public access key&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;awsSecretAccessKey&lt;/strong&gt; - your Amazon S3 secret access key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can encrypt your keys by &lt;a href=&quot;/assets/drencrypt.zip&quot;&gt;using this utility&lt;/a&gt; I have provided (extract zip, run bat file or convert to shell script). Incidentally, this can also be used to change your DR passwords (but don’t forget to change them on both sides of the wire).&lt;/p&gt;

&lt;h3 id=&quot;so-here-we-are&quot;&gt;So Here We Are&lt;/h3&gt;

&lt;p&gt;At this point you should have a working repository that integrates with an unlimited, web-based file system. If you’re a dork like me, you think this is great and are glad you just spent a couple hours of your free time figuring out how to set it up. Otherwise, you probably didn’t make it through the entire article.&lt;/p&gt;

&lt;p&gt;Amazon S3 seems to have some amazing potential. The entire field of distributed services is an exciting area that could eventually change the way we think about corporate IT, and even the way we do business (wow, I just sounded like a marketing bobble-head for a second there, didn’t I?). Hopefully this post will give you some interesting insight into ways you can leverage your portal implementation with some of these new technologies, or at the very least, has provided you with some information on S3.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Querying the Portal Database using the Server API</title>
   <link href="http://blog.hross.net/webcenter/2008/02/29/queryserverapi.html"/>
   <updated>2008-02-29T00:00:00+00:00</updated>
   <id>http://blog.hross.net/webcenter/2008/02/29/queryserverapi</id>
   <content type="html">&lt;p&gt;I love the &lt;a href=&quot;http://edocs.bea.com/alui/devdoc/docs60/References/Portal_API_Documentation.htm&quot;&gt;ALUI Server API&lt;/a&gt;. It’s robust, fairly easy to use, cross-platform, and powerful. Unfortunately, it still has its limitations. One of the biggest limitations is the same limitation that plagues most object models built on top of a database layer: its inability to be a database.&lt;/p&gt;

&lt;p&gt;No matter how brilliant the design of the object hierarchy, there will always be situations where running a SQL query to get information would be a lot simpler. Recently, I ran into one of those situations and was lucky enough to know how to circumvent the “rules”. What follows is an analysis of that situation.&lt;/p&gt;

&lt;p&gt;As usual, this post has me thinking I should just put “&lt;strong&gt;this is not supported&lt;/strong&gt;” in my blog description.&lt;/p&gt;

&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;

&lt;p&gt;I was recently asked, “How can I get a list of all the portlets currently in use in my portal?” In other words, could I come up with a table of pages, their parent communities, and the portlets on them for the entire portal.&lt;/p&gt;

&lt;p&gt;Using the server API, creating such a table is possible via the following logic:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Loop through all communities in the portal&lt;/li&gt;
  &lt;li&gt;For each community, get a list of pages&lt;/li&gt;
  &lt;li&gt;For each page, get a list of portlets.&lt;/li&gt;
  &lt;li&gt;Look up each portlet name and ID.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While this is certainly do-able, you don’t need &lt;a href=&quot;http://en.wikipedia.org/wiki/Big_O_notation&quot;&gt;big O notation&lt;/a&gt; to see the embedded for/each statements, large volume of data, and potential for this code to chew up a ton of CPU cycles and make quite a few database queries. In a production environment, this just doesn’t seem feasible.&lt;/p&gt;

&lt;p&gt;But oh, if I only had access to the database. I could write a complex query that would join a few tables together and give me what I want. One simple SQL statement. Here is the statement I would write to produce the described table:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SELECT pagegadgets.GADGETID, gadgets.NAME AS GADGETNAME,
communities.NAME AS COMMUNITYNAME, pages.NAME AS PAGENAME FROM 
PTPAGEGADGETS pagegadgets LEFT JOIN PTPAGES pages 
    ON pagegadgets.PAGEID=pages.OBJECTID 
INNER JOIN PTGADGETS gadgets 
    ON pagegadgets.GADGETID=gadgets.OBJECTID 
INNER JOIN PTCOMMUNITIES communities 
    ON pages.FOLDERID = communities.FOLDERID 
ORDER BY gadgets.NAME, communities.NAME, pages.NAME ASC
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Some of you are probably thinking, “How about I just create a remote portlet that directly connects to the portal database?”, which you can do, and some customers have. However, you lose the ability to combine this table with other API code, lose the portable nature of Sever API libraries, and the cross platform capability to execute the query.&lt;/p&gt;

&lt;p&gt;How about I show you a way to use the Server API instead?&lt;/p&gt;

&lt;h3 id=&quot;casting-to-an-internal-session&quot;&gt;Casting to an Internal Session&lt;/h3&gt;

&lt;p&gt;It turns out that this process is much easier than you think. The first part involves getting an internal session object. An internal session is simply a back end class that we aren’t expected to use. It provides a lot of goodies that aren’t available to normal server API IPTSession objects. To get an internal session, we simply need to know about it. This means the following imports:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;import com.plumtree.server.impl.core.PTSession;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;import com.plumtree.server.impl.core.InternalSession;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and casting an IPTSession object like so:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InternalSession iSession = ((PTSession) session).GetInternalSession();&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Congratulations. You have an internal session object. Intellisense will show you all kinds of undocumented goodies related to this object. Given the post topic, today we are mainly interested in the database querying ability…&lt;/p&gt;

&lt;h3 id=&quot;running-a-query&quot;&gt;Running a Query&lt;/h3&gt;

&lt;p&gt;I could write something long and witty to explain the rest of the code, but it hardly seems necessary. Here is the entire source (Java) for a tag which does as described. The SQL is hard coded into the example:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.bea.services.tags;

import com.plumtree.openkernel.db.IOKDBCursor;
import com.plumtree.openkernel.db.IOKDBResultSet;
import com.plumtree.openkernel.db.IOKDBRow;
import com.plumtree.portaluiinfrastructure.tags.ATag;
import com.plumtree.portaluiinfrastructure.tags.TagType;
import com.plumtree.portaluiinfrastructure.tags.metadata.*;
import com.plumtree.server.*;
import com.plumtree.server.impl.core.InternalSession;
import com.plumtree.server.impl.core.PTSession;
import com.plumtree.taskapi.portalui.TaskAPIUIUser;
import com.plumtree.uiinfrastructure.activityspace.AActivitySpace;
import com.plumtree.xpshared.htmlconstructs.PTStyleClass;
import com.plumtree.xpshared.htmlelements.*;

public class PortletLocationTag extends ATag {

    public static final ITagMetaData TAG = new TagMetaData(&quot;portletlist&quot;,
            &quot;This tag lists portlet locations.&quot;);

    public static final OptionalTagAttribute PORTLETID = new OptionalTagAttribute(
            &quot;portletId&quot;, &quot;A specific portlet ID to query by.&quot;,
            AttributeType.STRING, &quot;&quot;);
    
    public static final OptionalTagAttribute MAXROWS = new OptionalTagAttribute(
            &quot;maxRows&quot;, &quot;The maximum number of rows in the query.&quot;,
            AttributeType.STRING, &quot;500&quot;);

    private static final String PORTLET_LOCATION_QUERY = 
        &quot;SELECT pagegadgets.GADGETID, gadgets.NAME AS GADGETNAME, &quot;
            + &quot;communities.NAME AS COMMUNITYNAME, &quot;
            + &quot;pages.NAME AS PAGENAME FROM &quot;
            + &quot;PTPAGEGADGETS pagegadgets &quot;
            + &quot;LEFT JOIN PTPAGES pages ON &quot;
            + &quot;pagegadgets.PAGEID=pages.OBJECTID &quot;
            + &quot;INNER JOIN PTGADGETS gadgets ON &quot;
            + &quot;pagegadgets.GADGETID=gadgets.OBJECTID &quot;
            + &quot;INNER JOIN PTCOMMUNITIES communities ON &quot;
            + &quot;pages.FOLDERID = communities.FOLDERID &quot;;
    
    private static final String WHERE_PORTLET_ID = &quot;WHERE gadgets.OBJECTID = &quot;;
    private static final String ORDER_BY_GADGET = &quot; ORDER BY gadgets.NAME, communities.NAME, pages.NAME ASC&quot;;
    private static final String ORDER_BY_COMMUNITY = &quot; ORDER BY communities.NAME, pages.NAME ASC&quot;;

    public ATag Create() {
        return new PortletLocationTag();
    }

    public TagType GetTagType() {
        return TagType.SIMPLE;
    }

    public HTMLElement DisplayTag() {

        if (!hasAdminAccess()) {
            return null;
        } // they don't have access

        // create a table for our result set
        HTMLTable result = new HTMLTable();
        result.SetWidth(CommonHTMLStrings.ONE_HUNDRED_PERCENT);
        result.SetBorder(CommonHTMLStrings.ZERO);
        result.SetCellPadding(CommonHTMLStrings.ONE);
        result.SetCellSpacing(CommonHTMLStrings.ONE);

        // get the user session
        IPTSession session = getSession();
        InternalSession iSession = ((PTSession) session).GetInternalSession();

        // run the query as a cursor
        String query = PORTLET_LOCATION_QUERY;
        
        // build the query based on tag options
        if (getPortletId() &amp;amp;gt; 0) {
            query += WHERE_PORTLET_ID + getPortletId() + ORDER_BY_COMMUNITY;
        } else {
            query += ORDER_BY_GADGET;
        }
        
        // open the cursor and run it
        IOKDBCursor cursor = iSession.CreateCursor(query);
        int maxRows = getMaxRows();
        IOKDBResultSet results = cursor.Open(maxRows);
        
        // build the table of results
        HTMLTableRow tableRow = new HTMLTableRow();
        tableRow.SetStyleClass(PTStyleClass.LIST_SORT_HEADER_BG);

        HTMLTableCell tableCell = new HTMLTableCell();
        tableCell.SetVAlign(CommonHTMLStrings.MIDDLE);
        tableCell.SetStyleClass(PTStyleClass.LIST_SORT_HEADER);
        tableCell.AddInnerHTMLString(&quot;**Portlet Name**&quot;);
        tableRow.AddInnerHTMLElement(tableCell);
        
        tableCell = new HTMLTableCell();
        tableCell.SetVAlign(CommonHTMLStrings.MIDDLE);
        tableCell.SetStyleClass(PTStyleClass.LIST_SORT_HEADER);
        tableCell.AddInnerHTMLString(&quot;**Community Name**&quot;);
        tableRow.AddInnerHTMLElement(tableCell);
        
        tableCell = new HTMLTableCell();
        tableCell.SetVAlign(CommonHTMLStrings.MIDDLE);
        tableCell.SetStyleClass(PTStyleClass.LIST_SORT_HEADER);
        tableCell.AddInnerHTMLString(&quot;**Page Name**&quot;);
        tableRow.AddInnerHTMLElement(tableCell);
        
        result.AddInnerHTMLElement(tableRow);
        
        // output the results
        for (int i = 0; i new HTMLTableRow();
            
            // row coloring
            if ((0 == i) || (((i + 1) / 2) == (i / 2))) {
                // it's even
                tableRow.SetStyleClass(PTStyleClass.LIST_ITEM_TWO_BG);
            } else {
                // it's odd
                tableRow.SetStyleClass(PTStyleClass.LIST_ITEM_ONE_BG);
            }

            tableCell = new HTMLTableCell();
            tableCell.SetVAlign(CommonHTMLStrings.MIDDLE);
            tableCell.AddInnerHTMLString(dbrow.GetString(&quot;GADGETNAME&quot;));
            tableRow.AddInnerHTMLElement(tableCell);

            tableCell = new HTMLTableCell();
            tableCell.SetVAlign(CommonHTMLStrings.MIDDLE);
            tableCell.AddInnerHTMLString(dbrow.GetString(&quot;COMMUNITYNAME&quot;));
            tableRow.AddInnerHTMLElement(tableCell);
            
            tableCell = new HTMLTableCell();
            tableCell.SetVAlign(CommonHTMLStrings.MIDDLE);
            tableCell.AddInnerHTMLString(dbrow.GetString(&quot;PAGENAME&quot;));
            tableRow.AddInnerHTMLElement(tableCell);
            
            result.AddInnerHTMLElement(tableRow);
        }

        return result;
    }

    private int getPortletId() {
        try {
            return Integer.parseInt(GetTagAttributeAsString(PORTLETID));
        } catch (Exception ex) {
            return -1;
        }
    }

    private int getMaxRows() {
        try {
            return Integer.parseInt(GetTagAttributeAsString(MAXROWS));
        } catch (Exception ex) {
            return 0;
        }
    }
    
    private IPTSession getSession() {
        return (IPTSession) GetEnvironment().GetUserSession();
    }

    private boolean hasAdminAccess() {
        return TaskAPIUIUser.HasAdminLinkAccess((AActivitySpace) this
                .GetEnvironment());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h3 id=&quot;caveat-emptor&quot;&gt;Caveat Emptor&lt;/h3&gt;

&lt;p&gt;As &lt;a href=&quot;http://en.wikipedia.org/wiki/Uncle_Ben&quot;&gt;Uncle Ben&lt;/a&gt; would say, “With great power comes great responsibility.” The power I gave you above may also allow you to perform INSERT’s, UPDATE’s and DELETE’s, which I strongly caution against. Not only that, but the InternalSession object doesn’t perform all those nifty security checks that happen when we use a normal session (notice the hasAdminAccess function), so make sure you either do your own authentication, or limit the amount of information you provide.&lt;/p&gt;

&lt;p&gt;Happy querying.&lt;/p&gt;

</content>
 </entry>
 
 
</feed>