<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2207149810921558692</id><updated>2012-01-26T22:44:05.067-05:00</updated><category term='jackrabbit'/><category term='grails'/><category term='openarc'/><title type='text'>Rope on Fire</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>36</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-5412867518157708887</id><published>2011-12-26T23:17:00.002-05:00</published><updated>2011-12-26T23:33:56.435-05:00</updated><title type='text'>Upgrading to Grails 2.0</title><content type='html'>With the recent release of &lt;a href="http://grails.org/2.0.0+Release+Notes"&gt;grails 2.0&lt;/a&gt;, I upgraded OpenArc's &lt;a href="http://www.openarc.net/index.php?option=com_content&amp;view=article&amp;id=205&amp;Itemid=47"&gt;ILocker&lt;/a&gt; and &lt;a href="http://www.openarc.net/index.php?option=com_content&amp;view=article&amp;id=212&amp;Itemid=237"&gt;E-Arc&lt;/a&gt; software tonight. Ran into a few hurdles along the way and thought sharing them here might help someone else in the future.&lt;br /&gt;&lt;br /&gt;First, I ran into some dependency conflicts and had to add some new lines to grails-app/conf/BuildConfig.groovy:&lt;pre&gt;+      runtime ('edu.ucar:netcdf:4.2-min') {&lt;br /&gt;+        excludes 'slf4j-api', 'slf4j-simple'&lt;br /&gt;+      }&lt;br /&gt;+&lt;br /&gt;+      runtime ('org.apache.tika:tika-parsers:0.10') {&lt;br /&gt;+        excludes "commons-logging", "commons-codec"&lt;br /&gt;+      }&lt;br /&gt;+&lt;br /&gt;+      runtime ('org.xhtmlrenderer:core-renderer:R8') {&lt;br /&gt;+        excludes "itext", "commons-logging", "commons-codec"&lt;br /&gt;+      }&lt;br /&gt;&lt;/pre&gt;In particular, lots of trouble with "commons-logging" and "slf4j".&lt;br /&gt;&lt;br /&gt;Secondly, I'm using java7 (1.7.0_b147) and was getting the error "javac: target release 1.6 conflicts with default source release 1.7" so I add to throw:&lt;pre&gt;&lt;br /&gt;grails.project.source.level = 1.6&lt;/pre&gt; into grails-app/conf/BuildConfig.groovy as well.&lt;br /&gt;&lt;br /&gt;Finally and most perplexingly, I got everything running, but when I went to browse the application I just got an empty blank page, no error messages, nothing, just a blank page. Uggh. &lt;a href="http://www.grails.org/doc/2.0.x/guide/gettingStarted.html#upgradingFromPreviousVersionsOfGrails"&gt;Turns out&lt;/a&gt; you must run:&lt;pre&gt;grails install-templates&lt;/pre&gt; if you've installed the templates previously. It's documented in the upgrade notes -  would have been a nice thing to throw up a warning about too.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-5412867518157708887?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/5412867518157708887/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=5412867518157708887' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/5412867518157708887'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/5412867518157708887'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2011/12/upgrading-to-grails-20.html' title='Upgrading to Grails 2.0'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-3142754809770941232</id><published>2011-12-26T12:01:00.006-05:00</published><updated>2011-12-26T23:17:30.903-05:00</updated><title type='text'>JQuery Mobile for web-based forms applications</title><content type='html'>As a consulting company, &lt;a href="http://www.openarc.net"&gt;OpenArc&lt;/a&gt;, does a fair number of web-based forms applications for customers across a wide range of industries. In the last several months, we've really taken to using jQuery Mobile (JQM) for many of these applications.&lt;br /&gt;&lt;br /&gt;As is typical for applications of this type, a clean and usable interface is far more important to our customers than a sexy/flashy look and feel. JQM gives us an easy framework to produce a modern looking UI, tailored with the &lt;a href="http://jquerymobile.com/themeroller/"&gt;jQuery Mobile Themeroller&lt;/a&gt;, customized to match the client's brand requirements. Our clients are also very happy to know their applications can be accessed from a wide array of mobile devices.&lt;br /&gt;&lt;br /&gt;Here's a few screenshots from just one of these applications:&lt;br /&gt;&lt;br /&gt;First, a dashboard of sorts:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-RQhjnix4k10/TvjFoorzvwI/AAAAAAAAARY/OTHeyWil_14/s1600/dashboard.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 185px;" src="http://4.bp.blogspot.com/-RQhjnix4k10/TvjFoorzvwI/AAAAAAAAARY/OTHeyWil_14/s320/dashboard.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5690515431089225474" /&gt;&lt;/a&gt;&lt;br /&gt;A listing page, with the ever so useful "data-filter: true" attribute:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-FdhVZRzu0Jc/TvjF5a7dqJI/AAAAAAAAARk/q9mp0pK4eU8/s1600/listing.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 238px;" src="http://1.bp.blogspot.com/-FdhVZRzu0Jc/TvjF5a7dqJI/AAAAAAAAARk/q9mp0pK4eU8/s320/listing.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5690515719454566546" /&gt;&lt;/a&gt;&lt;br /&gt;Finally, a meeting edit page showing a time picker control, still in progress:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-rXEQdG5nYu8/TvjF_waKl3I/AAAAAAAAARw/zpRHXa5aEX0/s1600/meeting.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 179px;" src="http://4.bp.blogspot.com/-rXEQdG5nYu8/TvjF_waKl3I/AAAAAAAAARw/zpRHXa5aEX0/s320/meeting.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5690515828299700082" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;We've had a few glitches along the way, but in general, our clients are very pleased with a JQM based UI. This makes us very happy too!  &lt;br /&gt;&lt;br /&gt;One issue we saw early on was the default ajax-based navigation not playing well on IE*, so for now we've disabled it via:&lt;pre&gt;$.mobile.ajaxEnabled = false; $.mobile.pushStateEnabled = false;&lt;/pre&gt;Normally, dialog boxes in JQM do not require full HTML pages, just HTML snippets (e.g. :layout =&gt; nil) as they are loaded via AJAX and work like jQuery UI dialogs.&lt;br /&gt;&lt;br /&gt;However, when you set "$.mobile.ajaxEnabled = false" JQM will no longer load dialogs via ajax, EVEN if you set "data-ajax=true" on the dialog links. That seems like a bug to me, ignoring "data-ajax=true". A patch to fix the problem:&lt;pre&gt;diff --git js/jquery.mobile.navigation.js js/jquery.mobile.navigation.js&lt;br /&gt;index f85a491..181b9c9 100755&lt;br /&gt;--- js/jquery.mobile.navigation.js&lt;br /&gt;+++ js/jquery.mobile.navigation.js&lt;br /&gt;@@ -1322,10 +1322,11 @@&lt;br /&gt;                        var baseUrl = getClosestBaseUrl( $link ),&lt;br /&gt; &lt;br /&gt;                                //get href, if defined, otherwise default to empty hash&lt;br /&gt;-                               href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );&lt;br /&gt;+                               href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl ),&lt;br /&gt;+        isTargetDialog = $link.data("rel") === "dialog";&lt;br /&gt; &lt;br /&gt;                        //if ajax is disabled, exit early&lt;br /&gt;-                       if( !$.mobile.ajaxEnabled &amp;&amp; !path.isEmbeddedPage( href ) ){&lt;br /&gt;+                       if( !$.mobile.ajaxEnabled &amp;&amp; !isTargetDialog &amp;&amp; !path.isEmbeddedPage( href ) ){&lt;br /&gt;                                httpCleanup();&lt;br /&gt;                                //use default click handling&lt;br /&gt;                                return;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;My only hope at this point is to see an expanded set of controls/plugins, ideally such that we'd no longer have need of jQuery UI.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-3142754809770941232?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/3142754809770941232/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=3142754809770941232' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3142754809770941232'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3142754809770941232'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2011/12/jquery-mobile-for-web-based-forms.html' title='JQuery Mobile for web-based forms applications'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-RQhjnix4k10/TvjFoorzvwI/AAAAAAAAARY/OTHeyWil_14/s72-c/dashboard.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7363797311469211926</id><published>2010-10-08T12:06:00.004-04:00</published><updated>2010-10-08T12:41:20.973-04:00</updated><title type='text'>Grails and JCifs</title><content type='html'>&lt;a href="http://jcifs.samba.org/"&gt;JCIFS&lt;/a&gt; is:&lt;br /&gt;&lt;blockquote&gt;...an Open Source client library that implements the CIFS/SMB networking protocol in 100% Java. CIFS is the standard file sharing protocol on the Microsoft Windows platform.&lt;/blockquote&gt;As part of a project to provide schools and businesses with an open source solution to access their "My Documents" folder anytime/anywhere over the web, I recently had the pleasure of integrating JCIFS into my &lt;a href="http://www.grails.org"&gt;Grails&lt;/a&gt; application.&lt;br /&gt;&lt;br /&gt;The obligatory screenshot:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_55fIZQMSGNU/TK9HMvcdL2I/AAAAAAAAAHg/L-cgWeh3Gqg/s1600/ilocker-home.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 176px;" src="http://1.bp.blogspot.com/_55fIZQMSGNU/TK9HMvcdL2I/AAAAAAAAAHg/L-cgWeh3Gqg/s320/ilocker-home.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5525713552026709858" /&gt;&lt;/a&gt;&lt;br /&gt;I dropped the latest &lt;a href="http://jcifs.samba.org/src/"&gt;JCIFS jar file&lt;/a&gt; into my $GRAILS-APP/lib folder, and began implementing the "My Documents" feature against a samba server for starters. When I moved to a Windows 2008 server everything fell apart, with all operations started timing out. After some digging around in the &lt;a href="http://jcifs.samba.org/src/docs/api/overview-summary.html#scp"&gt;rather extensive set&lt;/a&gt; of config options, I realized I need the following in my grails config file:&lt;pre&gt;System.setProperty("jcifs.smb.client.dfs.disabled", "true");&lt;/pre&gt;Your environment may differ but make sure you take a good look at the JCIFS configuration options at least. &lt;br /&gt;&lt;br /&gt;Ok, so here's a simple example of removing a file:&lt;pre&gt;  void removeFile(WorkspacePath p)&lt;br /&gt;  {&lt;br /&gt;    def ntlm = new NtlmPasswordAuthentication("", p.username, p.password);&lt;br /&gt;    SmbFile file = new SmbFile(absoluteFilePath(p.url, p.path), ntlm);&lt;br /&gt;    file.delete();&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;Note: I pass "" as the first argument to NtlmPasswordAuthentication as the domain is part of p.username (e.g. joel@example.com).&lt;br /&gt;&lt;br /&gt;One thing you need to make sure of is always ending directory paths with a "/", otherwise you will get errors. Here's a more complicated example of a "eachFile" method that takes a closure as it's final argument:&lt;pre&gt;  public void eachFile(WorkspacePath p, Closure c)&lt;br /&gt;  {&lt;br /&gt;    println "eachFile ${p.url} - ${p.path}";&lt;br /&gt;    def path = absoluteDirPath(p.url, p.path);&lt;br /&gt;    def ntlm = new NtlmPasswordAuthentication("", p.username, p.password);&lt;br /&gt;    SmbFile file = new SmbFile(path, ntlm);&lt;br /&gt;&lt;br /&gt;    // are we dealing with a directory path or just a single file?&lt;br /&gt;    if (!file.isDirectory()) {&lt;br /&gt;      c.call([name: file.name, file: file, path: file.canonicalPath,&lt;br /&gt;              inputStream: { return new SmbFileInputStream(file); },&lt;br /&gt;              outputStream: { return new SmbFileOutputStream(file); } &lt;br /&gt;             ]);&lt;br /&gt;      return;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    file.listFiles().each { &lt;br /&gt;      f-&gt; if (f.isDirectory()) return;&lt;br /&gt;      if (f.isHidden()) return;&lt;br /&gt;&lt;br /&gt;      c.call([name: f.name, file: f, path: f.canonicalPath,&lt;br /&gt;              inputStream: { return new SmbFileInputStream(f); },&lt;br /&gt;              outputStream: { return new SmbFileOutputStream(f); } &lt;br /&gt;             ]);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;We've been quite pleased with JCIFS and it well its been working in our grails application. We are currently using 1.3.14 with the &lt;a href="http://old.nabble.com/How-to-make-jCIFS-1.3.14-more-stable-td28759449.html#a28759449"&gt;patches noted here&lt;/a&gt;. I just noticed that 1.3.15 is out so I'm interested in trying that as soon as possible!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7363797311469211926?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7363797311469211926/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7363797311469211926' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7363797311469211926'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7363797311469211926'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2010/10/grails-and-jcifs.html' title='Grails and JCifs'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_55fIZQMSGNU/TK9HMvcdL2I/AAAAAAAAAHg/L-cgWeh3Gqg/s72-c/ilocker-home.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-287613108767066612</id><published>2010-10-01T10:36:00.011-04:00</published><updated>2010-10-01T11:42:39.424-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='jackrabbit'/><category scheme='http://www.blogger.com/atom/ns#' term='openarc'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails and JackRabbit</title><content type='html'>Here's a brief overview of I plugged &lt;a href="http://jackrabbit.apache.org/"&gt;JackRabbit&lt;/a&gt;, a fully conforming implementation of the Java Content Repository specifications, into several of the &lt;a href="http://www.grails.org/"&gt;Grails&lt;/a&gt; based projects I've been working on recently.&lt;br /&gt;&lt;br /&gt;Currently, I'm using JackRabbit for user editable page content. Perhaps overkill, but I have plans to leverage additional JackRabbit features down the road.&lt;br /&gt;&lt;br /&gt;First off, there is a Grails JackRabbit plugin, but it looked rather old and un-maintained and had no real documentation, so I just rolled my own solution.&lt;br /&gt;&lt;br /&gt;Ok, so first, drop the jackrabbit jars into your $PROJ/lib/ folder.&lt;pre&gt;(~/src/ilocker) ls -1 lib/&lt;br /&gt;jackrabbit-api-2.1.1.jar&lt;br /&gt;jackrabbit-core-2.1.1.jar&lt;br /&gt;jackrabbit-jcr-commons-2.1.1.jar&lt;br /&gt;jackrabbit-jcr-server-2.1.1.jar&lt;br /&gt;jackrabbit-spi-2.1.1.jar&lt;br /&gt;jackrabbit-spi-commons-2.1.1.jar&lt;br /&gt;jcr-2.0.jar&lt;br /&gt;&lt;/pre&gt;An improved approach would be to add the appropriate directives to grails-app/conf/BuildConfig.groovy. But for now, this will work.&lt;br /&gt;&lt;br /&gt;Next you'll need an appropriately configured JackRabbit repository.xml file. I configured JackRabbit with a Postgresql &lt;a href="http://jackrabbit.apache.org/api/2.0/org/apache/jackrabbit/core/data/db/DbDataStore.html"&gt;DbDataStore&lt;/a&gt;. A sample of my configuration can be found &lt;a href="http://dev.openarc.net/public/repository.xml"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;So how to get started? I created a grails-app/service/ContentService.groovy, that starts out like this:&lt;br /&gt;&lt;pre&gt;import org.springframework.beans.factory.InitializingBean;&lt;br /&gt;import javax.jcr.Repository;&lt;br /&gt;import javax.jcr.Session;&lt;br /&gt;import javax.jcr.SimpleCredentials;&lt;br /&gt;import javax.jcr.Node;&lt;br /&gt;import org.apache.jackrabbit.core.TransientRepository;&lt;br /&gt;&lt;br /&gt;class ContentService implements InitializingBean&lt;br /&gt;{&lt;br /&gt; static scope = "singleton";&lt;br /&gt; def grailsApplication;&lt;br /&gt; Repository _repository;&lt;br /&gt;&lt;br /&gt; public void afterPropertiesSet() {&lt;br /&gt;   def jcr = grailsApplication.config.jcr;&lt;br /&gt;   _repository = new TransientRepository(jcr.repo.config, jcr.repo.home);&lt;br /&gt;   log.info "Configuring Content Service ... config=${jcr.repo.config}, home=${jcr.repo.home}";&lt;br /&gt; }&lt;/pre&gt;My grails-app/conf/Config.groovy file has the following entries:&lt;pre&gt;jcr.repo.home = "/var/lib/ilocker"&lt;br /&gt;jcr.repo.config = "/etc/ilocker/repository.xml"&lt;/pre&gt;So the line&lt;pre&gt;_repository = new TransientRepository(jcr.repo.config, jcr.repo.home);&lt;/pre&gt; above wires everything up to use /etc/ilocker/repository.xml and to set ${rep.home} = /var/lib/ilocker. Make sure the tomcat user has appropriate access to /var/lib/ilocker when you put the site into production!&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 0, 0);"&gt;Getting JackRabbit to work first time around can be a little dicey, because JackRabbit will copy the repository.xml to ${rep.home}/workspaces. If anything is misconfigured, it's easiest to just change repository.xml, delete ${rep.home}/workspaces, and try again. If you don't delete ${rep.home}/workspaces, your changes to repository.xml will have no effect (unless you create a new workspace). Take note!&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now to write content to our ContentService, I'm using:&lt;pre&gt;  public void put(String controller, String action, String data) {&lt;br /&gt;    Session session = _repository.login(new SimpleCredentials("username", "password".toCharArray()));&lt;br /&gt;&lt;br /&gt;    log.info "ContentService.put ${controller} ${action}";&lt;br /&gt;&lt;br /&gt;    try {&lt;br /&gt;      Node controllerNode = getControllerNode(session, controller);&lt;br /&gt;      Node node = getActionNode(controllerNode, action);&lt;br /&gt;      Calendar lastModified = Calendar.getInstance();&lt;br /&gt;&lt;br /&gt;      node.setProperty("jcr:lastModified", lastModified);&lt;br /&gt;      node.setProperty("jcr:mimeType", "text/html");&lt;br /&gt;      node.setProperty("jcr:encoding", "utf-8");&lt;br /&gt;      node.setProperty("jcr:data", data);&lt;br /&gt;&lt;br /&gt;      session.save();&lt;br /&gt;    } &lt;br /&gt;    finally {&lt;br /&gt;      session.logout();&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;Obviously, completely ignoring JackRabbit level security. To read content in my controllers, I write code like this for example:&lt;pre&gt;class AdminController {&lt;br /&gt;&lt;br /&gt;  def contentService;&lt;br /&gt;&lt;br /&gt;  def index = {&lt;br /&gt;    String content = contentService.get(controllerName, actionName);&lt;br /&gt;    [ chtml: content ]&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;And then in ContentService.groovy I have:&lt;pre&gt;  public String get(String controller, String action) {&lt;br /&gt;    Session session = _repository.login(new SimpleCredentials("username", "password".toCharArray()));&lt;br /&gt;    String value;&lt;br /&gt;&lt;br /&gt;    log.info "ContentService.get ${controller} ${action}";&lt;br /&gt;&lt;br /&gt;    try {&lt;br /&gt;      Node controllerNode = getControllerNode(session, controller);&lt;br /&gt;      Node actionNode = getActionNode(controllerNode, action);&lt;br /&gt;      if (actionNode.hasProperty("jcr:data")) {&lt;br /&gt;        value = actionNode.getProperty("jcr:data").getString();&lt;br /&gt;      }&lt;br /&gt;    } &lt;br /&gt;    finally {&lt;br /&gt;      session.logout();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    return value;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private Node getControllerNode(Session session, String controller) {&lt;br /&gt;    Node root = session.getRootNode();&lt;br /&gt;    if (root.hasNode(controller))&lt;br /&gt;      return root.getNode(controller);&lt;br /&gt;&lt;br /&gt;    Node node = root.addNode(controller, "nt:folder");&lt;br /&gt;    return node;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private Node getActionNode(Node parent, String action) {&lt;br /&gt;&lt;br /&gt;    if (parent.hasNode(action)) { &lt;br /&gt;      Node actionNode = parent.getNode(action)&lt;br /&gt;      return actionNode.getNode("jcr:content");&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    Node actionNode = parent.addNode(action, "nt:file");&lt;br /&gt;    Node content = actionNode.addNode("jcr:content", "nt:resource");&lt;br /&gt;    return content;&lt;br /&gt;  }&lt;/pre&gt;Again punting on JackRabbit level security. To preload my sites with default content, I wrote a simple groovy program to load the repository. I put jackrabbit-standalone-2.1.1.jar into $HOME/.groovy/lib/ then wrote a simple script, the heart of which is&lt;pre&gt;    _repository = new TransientRepository("/etc/ilocker/repository.xml", "/var/lib/ilocker/");&lt;br /&gt;&lt;br /&gt;    Session session = _repository.login(&lt;br /&gt;      new SimpleCredentials("username", "password".toCharArray()));&lt;br /&gt;&lt;br /&gt;    try {&lt;br /&gt;&lt;br /&gt;      File input = new File(args[0]);&lt;br /&gt;      input.eachLine &lt;br /&gt;      { line -&gt;&lt;br /&gt;        List&lt;String&gt; words = line.tokenize('\t');&lt;br /&gt;        println "Processing " + words[0] + "." + words[1];&lt;br /&gt;&lt;br /&gt;        Node home = getHomeNode(session, words[0]);&lt;br /&gt;        Node content = getContentNode(home, words[1]);&lt;br /&gt;&lt;br /&gt;        // store std. attributes&lt;br /&gt;        Calendar lastModified = Calendar.getInstance();&lt;br /&gt;        content.setProperty("jcr:lastModified", lastModified);&lt;br /&gt;        content.setProperty("jcr:mimeType", "text/html");&lt;br /&gt;        content.setProperty("jcr:encoding", "utf-8");&lt;br /&gt;&lt;br /&gt;        // store extended attributes&lt;br /&gt;        content.addMixin("mix:title");&lt;br /&gt;        content.setProperty("jcr:title", words[3]);&lt;br /&gt;&lt;br /&gt;        // store content&lt;br /&gt;        File data;&lt;br /&gt;        if (words[2].startsWith("/")) data = new File(words[2]);&lt;br /&gt;        else data = new File(scriptPath, words[2]);&lt;br /&gt;&lt;br /&gt;        String jcrData = data.getText();&lt;br /&gt;        content.setProperty("jcr:data", jcrData);&lt;br /&gt;&lt;br /&gt;        session.save();&lt;br /&gt;      }&lt;br /&gt;    } &lt;br /&gt;    finally {&lt;br /&gt;      session.logout();&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;. The full script can be found &lt;a href="http://dev.openarc.net/public/iloader.groovy"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Well I hope you found this a useful overview of integrating JackRabbit into a Grails application. The only trouble I've had in production with the above setup is when I had:&lt;pre&gt;    &amp;lt;SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex"&amp;gt;&lt;br /&gt;        &amp;lt;param name="path" value="${rep.home}/repository/index"/&amp;gt;&lt;br /&gt;        &amp;lt;param name="supportHighlighting" value="true"/&amp;gt;&lt;br /&gt;    &amp;lt;/SearchIndex&amp;gt;&lt;br /&gt;&lt;/pre&gt;In my repository.xml. Then I would get periodic repository locking errors when Lucene indexing kicked in. Since I'm not doing any JCR searching, I just deleted all Lucene search index nodes from my repository.xml.&lt;br /&gt;&lt;br /&gt;This work was done for &lt;a href="http://www.openarc.net"&gt;OpenArc&lt;/a&gt;, a Pittsburgh-based open source consulting firm with clients in Pittsburgh, Chicago, and D.C.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-287613108767066612?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/287613108767066612/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=287613108767066612' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/287613108767066612'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/287613108767066612'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2010/10/grails-and-jackrabbit.html' title='Grails and JackRabbit'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-8035936985135325644</id><published>2009-08-12T22:24:00.004-04:00</published><updated>2009-08-12T22:38:01.959-04:00</updated><title type='text'>Updated NetCenter Screenshots</title><content type='html'>Ok, no reams of code in this post, just some recent screenshots of &lt;a href="http://www.netserve365.com/netcenter365.php"&gt;NetCenter&lt;/a&gt;, an ajax rich  jquery/Grails based CRM I've been working on. Most of the icons below come from the &lt;a href="http://commons.wikimedia.org/wiki/User:Paintpot/Icons#Crystal_Clear"&gt;CrystalClear icon set&lt;/a&gt; on wikimedia.&lt;br /&gt;&lt;br /&gt;This first shot shows our TODO manager rollup/down side bar:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_55fIZQMSGNU/SoN5pabs1WI/AAAAAAAAAGU/yDOGSNSkO6E/s1600-h/actionitems.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 198px;" src="http://3.bp.blogspot.com/_55fIZQMSGNU/SoN5pabs1WI/AAAAAAAAAGU/yDOGSNSkO6E/s320/actionitems.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5369268933132866914" /&gt;&lt;/a&gt;&lt;br /&gt;And the asset management module:&lt;br /&gt; &lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_55fIZQMSGNU/SoN5xQmf8bI/AAAAAAAAAGc/R6OTB8kj3ls/s1600-h/asset.pics.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 186px;" src="http://2.bp.blogspot.com/_55fIZQMSGNU/SoN5xQmf8bI/AAAAAAAAAGc/R6OTB8kj3ls/s320/asset.pics.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5369269067932758450" /&gt;&lt;/a&gt;&lt;br /&gt;Who are those cute kids ;-) ?&lt;br /&gt;&lt;br /&gt;And finally, the document management accordion panel for an account:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_55fIZQMSGNU/SoN5kqKonXI/AAAAAAAAAGM/theuIwqqVwI/s1600-h/accountdocs.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="http://4.bp.blogspot.com/_55fIZQMSGNU/SoN5kqKonXI/AAAAAAAAAGM/theuIwqqVwI/s320/accountdocs.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5369268851456908658" /&gt;&lt;/a&gt;&lt;br /&gt;Seeing the product live is far more impressive - new tab load speed, yahoo map popups, click to call - but hopefully these screenshots give you a sense of the general UI layout of NetCenter. This is really the first time I've down a tab oriented layout but I thought it would be the best design for a web-based CRM solution where you are jumping around alot, with multiple ways to get to the same information, but don't want to lose your place.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-8035936985135325644?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/8035936985135325644/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=8035936985135325644' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8035936985135325644'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8035936985135325644'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2009/08/updated-netcenter-screenshots.html' title='Updated NetCenter Screenshots'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_55fIZQMSGNU/SoN5pabs1WI/AAAAAAAAAGU/yDOGSNSkO6E/s72-c/actionitems.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-3453477182622733790</id><published>2009-07-16T14:44:00.004-04:00</published><updated>2009-07-16T15:28:47.700-04:00</updated><title type='text'>Document Management in NetCenter</title><content type='html'>Although our mid to long term plans for NetCenter365 include Sharepoint and Alfresco integration, we currently provide a more streamlined, account oriented, document management capability within NetCenter that we think might better serve some organizations.&lt;br /&gt;&lt;br /&gt;Documents in NetCenter are attached to customer records or accounts. Here's a screenshot:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_55fIZQMSGNU/Sl94V4u1veI/AAAAAAAAAGE/hye40SBM8Hg/s1600-h/accountdocs.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="http://2.bp.blogspot.com/_55fIZQMSGNU/Sl94V4u1veI/AAAAAAAAAGE/hye40SBM8Hg/s320/accountdocs.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5359134398995611106" /&gt;&lt;/a&gt;&lt;br /&gt;On the backend, I created a C++/&lt;a href="http://fuse.sourceforge.net/"&gt;FUSE&lt;/a&gt; based filesystem. When you mount it you see a list of customer names as directories, under which documents attached to the accounts are found. This metadata is stored in the NetCenter database while the actual file contents are simply stored in a backing ext3 filesystem. This way it's easy to backup and restore, replicate, etc. Here's a snippet from account_node::readdir()&lt;pre&gt; int account_node::readdir(void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)&lt;br /&gt; {&lt;br /&gt;  filler(buf, ".", NULL, 0);&lt;br /&gt;  filler(buf, "..", NULL, 0);&lt;br /&gt;&lt;br /&gt;  pqxx::connection db(connect_string());&lt;br /&gt;  pqxx::nontransaction work(db);&lt;br /&gt;  pqxx::result result = work.exec("SELECT name,id,trunc(date_part('epoch',last_updated)),path FROM document where account_id=" + id());&lt;br /&gt;&lt;br /&gt;  std::string did; long lctm; std::string rpath;&lt;br /&gt;  for (pqxx::result::const_iterator r = result.begin(); r != result.end(); ++r)&lt;br /&gt;  {&lt;br /&gt;   filler(buf, r[0].c_str(), NULL, 0);&lt;br /&gt;   did = r[1].c_str();&lt;br /&gt;   r[2].to(lctm);&lt;br /&gt;   rpath = r[3].c_str();&lt;br /&gt;&lt;br /&gt;   std::string path = _path + "/" + r[0].c_str();&lt;br /&gt;   _filesystem-&gt;set_attributes(path, attributes(did, lctm, rpath));&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  return 0;&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;Whereas the code to read the actual file contents, looks something like this:&lt;pre&gt; int poi_node::open(struct fuse_file_info *fi)&lt;br /&gt; {&lt;br /&gt;  std::string fpath = full_path();&lt;br /&gt;&lt;br /&gt;  int res = ::open(fpath.c_str(), fi-&gt;flags);&lt;br /&gt;  if (res == -1)&lt;br /&gt;   return -errno;&lt;br /&gt;&lt;br /&gt;  ::close(res);&lt;br /&gt;  return 0;&lt;br /&gt; }&lt;/pre&gt;With the virtual filesystem mounted, we simply serve it up via Apache webdav and since we store the document metadata in the NetCenter database it's very easy to provide the frontend UI via &lt;a href="http://www.grails.org/"&gt;grails&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As far as the frontend goes, one big complaint we've heard about other document management solutions is how confusing it is for some users to download a file, find it on their hard drive, edit it, go back to their browser, and upload a new version. That's a very frustrating set of steps for many users.&lt;br /&gt;&lt;br /&gt;We built a very simple &lt;a href="https://jetpack.mozillalabs.com/"&gt;JetPack&lt;/a&gt; based extension for Firefox that registers a "webdav://" protocol handler that passes off such links to OpenOffice which &lt;b&gt;already knows&lt;/b&gt; how to handle them properly such that there is no downloading, finding, editing, and re-uploading. OpenOffice will directly save the document back to our Apache webdav server that sits on top of the NetCenter virtual filesystem discussed above.&lt;br /&gt;&lt;br /&gt;For Internet Explorer, we wrote a small C# based protocol handler that does almost the same thing but handles Microsoft Word or OpenOffice. Not quite as nice as the Firefox solution, but we can push out the MSI via AD group policy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-3453477182622733790?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/3453477182622733790/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=3453477182622733790' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3453477182622733790'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3453477182622733790'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2009/07/document-management-in-netcenter.html' title='Document Management in NetCenter'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_55fIZQMSGNU/Sl94V4u1veI/AAAAAAAAAGE/hye40SBM8Hg/s72-c/accountdocs.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7778794226221141855</id><published>2009-07-14T13:20:00.006-04:00</published><updated>2009-07-14T15:51:44.507-04:00</updated><title type='text'>Grails, jQuery, and Yahoo Maps</title><content type='html'>I recently completed a new &lt;a href="http://www.netserve365.com/netcenter365.php"&gt;NetCenter365&lt;/a&gt; feature that uses &lt;a href="http://developer.yahoo.com/maps/"&gt;Yahoo Maps&lt;/a&gt; to show the location of all current customers. Here's a screenshot:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_55fIZQMSGNU/SlzhmdwZRwI/AAAAAAAAAF8/KgwcNdzJbG8/s1600-h/mapreport.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 274px;" src="http://1.bp.blogspot.com/_55fIZQMSGNU/SlzhmdwZRwI/AAAAAAAAAF8/KgwcNdzJbG8/s320/mapreport.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5358405707602937602" /&gt;&lt;/a&gt;&lt;br /&gt;I really appreciate Yahoo's "Maps Web Services" which include a helpful &lt;a href="http://en.wikipedia.org/wiki/Geolocation"&gt;geolocation service&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;First, we map out HQ with: &lt;br /&gt;&lt;pre&gt; var map = new YMap(document.getElementById('map'));&lt;br /&gt; map.addTypeControl(); map.addZoomLong(); map.addPanControl();&lt;br /&gt; map.setMapType(YAHOO_MAP_REG);&lt;br /&gt;&lt;br /&gt; var hq = new YGeoPoint(HQ.latitude, HQ.longitude); &lt;br /&gt; map.drawZoomAndCenter(hq, 11); &lt;br /&gt;&lt;/pre&gt;Then we use grails and jquery to loop through every customer and fire off the following ajax requests:&lt;br /&gt;&lt;pre&gt; var url = '${createLink(controller: "location", action: "latlong")}' + "/";&lt;br /&gt;&lt;br /&gt; &amp;lt;g:each var="account" in="${accounts}"&amp;gt;&lt;br /&gt;  $.getJSON(url + ${account.id}, function(x) { &lt;br /&gt;    var pt = new YGeoPoint(x.latitude, x.longitude); &lt;br /&gt;    var m = new YMarker(pt); &lt;br /&gt;    m.addAutoExpand('${account.name.encodeAsJavaScript()}');&lt;br /&gt;    map.addOverlay(m); &lt;br /&gt;  });&lt;br /&gt; &amp;lt;/g:each&amp;gt;&lt;br /&gt;&lt;/pre&gt;The heart of the location/latlong method uses Yahoo's geolocation services. Here's a snippet of the groovy code:&lt;pre&gt; def geocoder = "http://local.yahooapis.com/MapsService/V1/geocode?appid=${APPID}"&lt;br /&gt; if (account.line1) geocoder += "&amp;street=" + URLEncoder.encode(account.line1);&lt;br /&gt; if (account.city) geocoder += "&amp;city=" + URLEncoder.encode(account.city);&lt;br /&gt; if (account.state) geocoder += "&amp;state=" + account.state;&lt;br /&gt; if (account.zip) geocoder += "&amp;zip=" + account.zip;&lt;br /&gt;&lt;br /&gt; def xml = geocoder.toURL().text&lt;br /&gt; def records = new XmlParser().parseText(xml);&lt;br /&gt; location.latitude = records.Result[0].Latitude.text()&lt;br /&gt; location.longitude = records.Result[0].Longitude.text()&lt;br /&gt;&lt;/pre&gt;Performance wise, the map pops up quite quickly and the markers appear in rapid procession. This is aided by caching Lat/Long info to minimize geolocation requests.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7778794226221141855?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7778794226221141855/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7778794226221141855' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7778794226221141855'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7778794226221141855'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2009/07/grails-jquery-and-yahoo-maps.html' title='Grails, jQuery, and Yahoo Maps'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_55fIZQMSGNU/SlzhmdwZRwI/AAAAAAAAAF8/KgwcNdzJbG8/s72-c/mapreport.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-4068530542373955155</id><published>2009-06-01T08:48:00.010-04:00</published><updated>2009-06-01T10:57:14.977-04:00</updated><title type='text'>CRM Integration via LDAP</title><content type='html'>Our vision for &lt;a href="http://www.netserve365.com/netcenter365.php"&gt;NetCenter&lt;/a&gt; is to facilitate and drive a &lt;span style="font-weight:bold;"&gt;customer centric view&lt;/span&gt; of day to day activities within an organization. Whether you're in sales, engineering, administration, or elsewhere, we want to help organize your documents, emails, phone calls, projects, and other day to day activities in a customer centric way.&lt;br /&gt;&lt;br /&gt;We also want a platform that's &lt;span style="font-weight:bold;"&gt;easy to use and integrates well into existing business systems&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;As part of this effort, I recently completed exposing NetCenter contacts to mail clients like &lt;a href="www.zimbra.com"&gt;Zimbra&lt;/a&gt;, Outlook, and &lt;a href="http://www.mozillamessaging.com/thunderbird/"&gt;Thunderbird&lt;/a&gt; via a custom &lt;a href="www.openldap.org/"&gt;OpenLDAP&lt;/a&gt; &lt;a href="http://www.openldap.org/doc/admin24/backends.html"&gt;backend&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;All of these mail clients can leverage LDAP based address books, so we expose NetCenter contacts via LDAP so that you can quickly and easily send emails to prospective and current customers. Here's a screenshot from Outlook:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_55fIZQMSGNU/SiPXCB0KMAI/AAAAAAAAAFk/hgDv_33C6JY/s1600-h/outlook.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 235px;" src="http://4.bp.blogspot.com/_55fIZQMSGNU/SiPXCB0KMAI/AAAAAAAAAFk/hgDv_33C6JY/s320/outlook.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5342350012838457346" /&gt;&lt;/a&gt;&lt;br /&gt;And Zimbra:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_55fIZQMSGNU/SiPrjnYywsI/AAAAAAAAAFs/mBENunHAAlk/s1600-h/zimbra.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 197px;" src="http://2.bp.blogspot.com/_55fIZQMSGNU/SiPrjnYywsI/AAAAAAAAAFs/mBENunHAAlk/s320/zimbra.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5342372580092461762" /&gt;&lt;/a&gt;&lt;br /&gt;There's no real documentation on how to create a custom backend, but the &lt;a href="http://www.openldap.org/doc/admin24/backends.html#Null"&gt;back-null&lt;/a&gt; and &lt;a href="http://www.openldap.org/doc/admin24/backends.html#Perl/Shell"&gt;back-shell&lt;/a&gt; backends are pretty good places to start.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-4068530542373955155?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/4068530542373955155/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=4068530542373955155' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/4068530542373955155'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/4068530542373955155'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2009/06/crm-integration-via-ldap.html' title='CRM Integration via LDAP'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_55fIZQMSGNU/SiPXCB0KMAI/AAAAAAAAAFk/hgDv_33C6JY/s72-c/outlook.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-8550742375475177521</id><published>2009-04-24T08:46:00.008-04:00</published><updated>2009-04-24T10:06:58.727-04:00</updated><title type='text'>Incoming call screen pops with sipX, rabbitMQ, and Adobe Air</title><content type='html'>I just finished the first beta of NetCenterPlus, an &lt;a href="http://www.adobe.com/products/air/develop/ajax/"&gt;Adobe Air html based tray application that presents &lt;/a&gt;&lt;a href="http://en.wikipedia.org/wiki/Screen_pop"&gt;screen pops&lt;/a&gt; for incoming calls on &lt;a href="http://www.sipfoundry.org/"&gt;sipX systems&lt;/a&gt;. NetCenterPlus is part of NetCenter, a CRM/Business Productivity solution from &lt;a href="http://www.netserve365.com/"&gt;NetServe365&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Here's a screenshot of the notification window on an incoming call. &lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_55fIZQMSGNU/SfG6SIZpzhI/AAAAAAAAAE0/H2384dx6qOc/s1600-h/ncplus-call.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 235px;" src="http://1.bp.blogspot.com/_55fIZQMSGNU/SfG6SIZpzhI/AAAAAAAAAE0/H2384dx6qOc/s320/ncplus-call.jpg" alt="" id="BLOGGER_PHOTO_ID_5328244654811500050" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;On the backend, I implemented a solution very similar to the one I did for &lt;a href="http://ropeonfire.blogspot.com/2009/01/integrating-sipx-with-ejabberd.html"&gt;Integrating sipx with ejabberd&lt;/a&gt;. There are two database triggers installed into the SIPXCDR database, the second of which is a PostgreSQL plperlu trigger which uses &lt;a href="http://search.cpan.org/dist/Net-Stomp/lib/Net/Stomp.pm"&gt;Net::Stomp&lt;/a&gt; to send a message to our rabbitMQ server indicating the callerId of an incoming call to the user registered for the destination extension. Not many of lines of code:&lt;pre&gt;CREATE FUNCTION cse_ncplus_change() RETURNS trigger AS $end$&lt;br /&gt;use Net::Stomp;&lt;br /&gt;&lt;br /&gt;my ($domain, $uid, $pwd) = @{$_TD-&gt;{args}};&lt;br /&gt;my $msg = TD-&gt;{"new"}{"from_id"};&lt;br /&gt;&lt;br /&gt;my $stomp = Net::Stomp-&gt;new({hostname=&gt;'mq.nvizn.com', port=&gt;'61613'});&lt;br /&gt;$stomp-&gt;connect({login=&gt;$uid, passcode=&gt;$pwd});&lt;br /&gt;&lt;br /&gt;my $uid = $_TD-&gt;{"new"}{"username"};&lt;br /&gt;$stomp-&gt;send({destination=&gt;"/$domain/ncplus/$uid",&lt;br /&gt;       body=&gt;($msg)});&lt;br /&gt;&lt;br /&gt;$stomp-&gt;disconnect;&lt;br /&gt;return undef;&lt;br /&gt;$end$&lt;br /&gt;LANGUAGE plperlu;&lt;/pre&gt;The other plpgsql trigger looks up the destination extension and munges up a nice looking incoming call number. That exercise is left to the reader.&lt;br /&gt;&lt;br /&gt;Now we've got a message on a per-user queue for every incoming call on our sipX system. So what next?&lt;br /&gt;&lt;br /&gt;I wanted an easy to deploy, cross platform, tray application that would listen for incoming messages on present the screen pop. I looked at &lt;a href="http://labs.mozilla.com/projects/prism/"&gt;Mozilla Prism&lt;/a&gt;, &lt;a href="http://silverlight.net/"&gt;Silverlight&lt;/a&gt;, and &lt;a href="http://www.adobe.com/products/air/"&gt;Adobe Air&lt;/a&gt;. Air was not my first choice to be honest, but the Prism project seems to have stagnated afaict, and Silverlight 2.0 on Linux doesn't look like it will be out anytime soon, so I went with Air. After spending some time with the product, I've definitely grown in my appreciation of its ease of use and design. It's &lt;span style="font-weight:bold;"&gt;really&lt;/span&gt; nice to be able to leverage existing web development skills to build these type of applications.&lt;br /&gt;&lt;br /&gt;So what does the Air application do? First of it, I used air.Socket and javascript to implement a &lt;a href="http://stomp.codehaus.org/"&gt;STOMP&lt;/a&gt; client. &lt;br /&gt;&lt;br /&gt;First the connection code:&lt;pre&gt;air.trace("setting up MessageQueue...");&lt;br /&gt;  this.socket = new air.Socket();&lt;br /&gt;  var self = this;&lt;br /&gt;&lt;br /&gt;this.socket.addEventListener(air.Event.CONNECT, function(event) {&lt;br /&gt;    self.sendCommand("CONNECT\nlogin:guest\npasscode:" + password + "\n\n");&lt;br /&gt;    self.state = self.STATE.CONNECT;&lt;br /&gt;  });&lt;/pre&gt;The main listener loop looks something like this:&lt;pre&gt;this.socket.addEventListener(air.ProgressEvent.SOCKET_DATA,&lt;br /&gt;                               function(event) {&lt;br /&gt;                                                                              &lt;br /&gt;    switch (self.state) {&lt;br /&gt;    case self.STATE.CONNECT:&lt;br /&gt;      self.subscribe();&lt;br /&gt;      break;&lt;br /&gt;    case self.STATE.READY:&lt;br /&gt;      var data = event.target.readUTFBytes(event.target.bytesAvailable);&lt;br /&gt;      var lines = data.split("\n");&lt;br /&gt;      if (lines[0] == "MESSAGE" &amp;&amp; lines.length&gt;5) {&lt;br /&gt;        msg_callback(lines[6]);&lt;br /&gt;      }&lt;br /&gt;      break;&lt;br /&gt;    }&lt;br /&gt;  });&lt;br /&gt;&lt;/pre&gt;So a NetCenterPlus user installs the application via a web page (yet to be prettied up!).&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_55fIZQMSGNU/SfHCttjrJTI/AAAAAAAAAE8/UtqOwZyZdxQ/s1600-h/ncplus-install-page2.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 235px;" src="http://1.bp.blogspot.com/_55fIZQMSGNU/SfHCttjrJTI/AAAAAAAAAE8/UtqOwZyZdxQ/s320/ncplus-install-page2.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5328253924735132978" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Then, the user enters their NetCenter username and password (again, this dialog needs some UI love. Did I mention I'm not a graphic artist?):&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_55fIZQMSGNU/SfHDJaPlmpI/AAAAAAAAAFE/zZvpTYB79xE/s1600-h/ncplus-config.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 235px;" src="http://2.bp.blogspot.com/_55fIZQMSGNU/SfHDJaPlmpI/AAAAAAAAAFE/zZvpTYB79xE/s320/ncplus-config.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5328254400586947218" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You can read and write to a local encrypted store in Air via functions like this:&lt;pre&gt;readFromLocalStore = function(key, defstr) {                                &lt;br /&gt;  var item = air.EncryptedLocalStore.getItem(key);                            &lt;br /&gt;  if (item == null) return defstr;                                            &lt;br /&gt;  return item.readUTFBytes(item.length);                                      &lt;br /&gt;  }                                                                           &lt;br /&gt;                                                                              &lt;br /&gt;saveToLocalStore = function(key, value) {                                   &lt;br /&gt;  var bytes = new air.ByteArray();                                            &lt;br /&gt;  bytes.writeUTFBytes(value);                                                 &lt;br /&gt;  air.EncryptedLocalStore.setItem(key, bytes);                                &lt;br /&gt;  } &lt;/pre&gt;When NetCenterPlus receives an incoming screen pop, we use the DOM to set the incoming call caller id, then we do an authenticated HTTP GET on the NetCenter REST based API to lookup the contact's name. The code looks something like this:&lt;pre&gt;    var cpnum = this.document.getElementById("callpop_number");&lt;br /&gt;    cpnum.innerHTML = fnum;&lt;br /&gt;&lt;br /&gt;    var url = "http://" + server + "/api/contact/byPhone/" + callnum;&lt;br /&gt;    var request = new air.URLRequest(url);&lt;br /&gt;&lt;br /&gt;    var loader = new air.URLLoader();&lt;br /&gt;    var self = this; var loader_sucess = true;&lt;br /&gt;                                                                              &lt;br /&gt;    loader.addEventListener(air.IOErrorEvent.IO_ERROR, function(error) {&lt;br /&gt;        air.trace("Failed to load: " + url);&lt;br /&gt;    });&lt;br /&gt;&lt;br /&gt;    loader.addEventListener(air.Event.COMPLETE, function(event) {&lt;br /&gt; var data = new air.URLVariables(event.target.data);&lt;br /&gt;        var cpname = self.document.getElementById("callpop_name");&lt;br /&gt; cpname.innerHTML = data.name;&lt;br /&gt;    });&lt;/pre&gt;You setup the login credentials in Air, with a single line of code:&lt;pre&gt;air.URLRequestDefaults.setLoginCredentialsForHost(this.server, username, password);&lt;/pre&gt;The NetCenter CRM is a &lt;a href="http://grails.org/"&gt;Grails application&lt;/a&gt; that exposes a REST based api via basic authentication tied into Active Directory. To set this up, I added the following lines to grails-app/conf/Config.groovy:&lt;pre&gt;jsecurity.filter.config = """                                                 &lt;br /&gt;                                                                              &lt;br /&gt;[filters]                                                                     &lt;br /&gt;authcBasic = org.jsecurity.web.filter.authc.BasicHttpAuthenticationFilter     &lt;br /&gt;authcBasic.applicationName = NetCenter API                                    &lt;br /&gt;                                                                              &lt;br /&gt;[urls]                                                                        &lt;br /&gt;/api/** = authcBasic                                                          &lt;br /&gt;"""&lt;/pre&gt;The contact controller "byPhone" method that the Air application uses is a very simple:&lt;pre&gt;    def byPhone = {&lt;br /&gt;      def contacts = Contact.withCriteria {&lt;br /&gt;        eq('active', true)&lt;br /&gt;        eq('licensee.id', session.lid)&lt;br /&gt;        or {&lt;br /&gt;          eq('workPhone', params.id)&lt;br /&gt;          eq('homePhone', params.id)&lt;br /&gt;          eq('cellPhone', params.id)&lt;br /&gt;        }&lt;br /&gt;      }                                                                       &lt;br /&gt;&lt;br /&gt;      return [ 'contacts': contacts ]&lt;br /&gt;    }&lt;/pre&gt;Well that about describes how all these parts come together. We plan on adding a lot more functionality to the NetCenterPlus Air application and thus far I'm pretty pleased with the Air platform.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-8550742375475177521?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/8550742375475177521/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=8550742375475177521' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8550742375475177521'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8550742375475177521'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2009/04/incoming-call-screen-pops-with-sipx.html' title='Incoming call screen pops with sipX, rabbitMQ, and Adobe Air'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_55fIZQMSGNU/SfG6SIZpzhI/AAAAAAAAAE0/H2384dx6qOc/s72-c/ncplus-call.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-6792204858511692668</id><published>2009-04-07T17:14:00.002-04:00</published><updated>2009-04-07T17:33:46.511-04:00</updated><title type='text'>NetCenter Click to Call</title><content type='html'>I just completed adding "Click to Call" functionality to NetCenter. Since this is a bit difficult to demonstrate with screenshots, I made a &lt;a href="http://www.youtube.com/watch?v=W0uLxIc2IbI"&gt;YouTube video&lt;/a&gt; instead.&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/W0uLxIc2IbI&amp;hl=en&amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/W0uLxIc2IbI&amp;hl=en&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;I implemented "Click to Call" using &lt;a href="http://ragstorooks.wordpress.com/2008/05/09/what-is-aloha/"&gt;Aloha&lt;/a&gt; and &lt;a href="http://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt;, testing the solution on &lt;a href="http://www.sipfoundry.org"&gt;sipX&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I have a grails PlaceCallService that sets up a connection to our RabbitMQ instance like this:&lt;pre&gt;  static transactional = false;&lt;br /&gt;  ConnectionParameters connectionParameters;&lt;br /&gt;  ConnectionFactory connectionFactory;&lt;br /&gt;  ConfigObject config = ConfigurationHolder.config;&lt;br /&gt;&lt;br /&gt;  MessageQueueService() {&lt;br /&gt;    connectionParameters = new ConnectionParameters();&lt;br /&gt;    connectionParameters.setUsername(config.mq.username);&lt;br /&gt;    connectionParameters.setPassword(config.mq.password);&lt;br /&gt;    connectionFactory = new ConnectionFactory(connectionParameters);&lt;br /&gt;  }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The actual message publish function looks something like this:&lt;pre&gt;  def publish(message) {&lt;br /&gt;    try {&lt;br /&gt;      Connection conn = connectionFactory.newConnection(config.mq.host,&lt;br /&gt;                                                        AMQP.PROTOCOL.PORT);&lt;br /&gt;      Channel ch = conn.createChannel();&lt;br /&gt;&lt;br /&gt;      ch.queueDeclare(config.placeCall.routingKey);&lt;br /&gt;      ch.basicPublish("", config.placeCall.routingKey, null,&lt;br /&gt;                      message.getBytes());                                    &lt;br /&gt;&lt;br /&gt;      ch.close();&lt;br /&gt;      conn.close();&lt;br /&gt;    }&lt;br /&gt;    catch (Exception e) {&lt;br /&gt;      log.error("Main thread caught exception: " + e);&lt;br /&gt;      return false&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    return true&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then in a "Third Party call initiator daemon", I unpack the message and use the Aloha stack to do a&lt;pre&gt;        try {&lt;br /&gt;            OutboundCallLegBean outboundCallLegBean = (OutboundCallLegBean)&lt;br /&gt;                applicationContext.getBean("outboundCallLegBean");&lt;br /&gt;            CallBean callBean = (CallBean)&lt;br /&gt;                applicationContext.getBean("callBean");&lt;br /&gt;            callBean.addCallListener(this);&lt;br /&gt;&lt;br /&gt;            // create two call legs                                           &lt;br /&gt;            String callLegId1 =&lt;br /&gt;                outboundCallLegBean.createCallLeg(URI.create(callee),&lt;br /&gt;                                                  URI.create(caller));&lt;br /&gt;            String callLegId2 =&lt;br /&gt;                outboundCallLegBean.createCallLeg(URI.create(caller),&lt;br /&gt;                                                  URI.create(callee));&lt;br /&gt;&lt;br /&gt;            // join the call legs                                             &lt;br /&gt;            System.out.println(String.format("connecting %s and %s in call...$&lt;br /&gt;            System.out.println(callBean.joinCallLegs(callLegId1, callLegId2))$&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This chunk of code is based on the helpful &lt;a href="https://trac.osmosoft.com/Aloha/browser/trunk/Samples/src/main/java/com/bt/springring/samples/SimpleThirdPartyCall.java?rev=96"&gt;Third Party Call&lt;/a&gt; sample from Aloha's subversion repository.&lt;br /&gt;&lt;br /&gt;Anyway, it's working well, consumes minimal resources on the web server (just post message to the "/placeCall/request" queue), and only took a few days to setup and deploy into production. Many thanks to the Aloha team, RabbitMQ folks, and sipX gurus.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-6792204858511692668?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/6792204858511692668/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=6792204858511692668' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/6792204858511692668'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/6792204858511692668'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2009/04/netcenter-click-to-call.html' title='NetCenter Click to Call'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-9213598122545599124</id><published>2009-03-06T07:25:00.011-05:00</published><updated>2009-03-06T08:07:15.942-05:00</updated><title type='text'>NetCenter CRM</title><content type='html'>For the last month or so, I've been working on "NetCenter" a &lt;a href="http://www.grails.org"&gt;Grails 1.1&lt;/a&gt; based CRM system that will integrate with &lt;a href="http://www.sipfoundry.org"&gt;sipX&lt;/a&gt; for call detail records, &lt;a href="http://www.zimbra.com"&gt;Zimbra&lt;/a&gt; or Exchange 2007 for email, calendaring, and time tracking purposes, and finally &lt;a href="http://www.alfresco.com"&gt;Alfresco&lt;/a&gt; or Sharepoint for document management.&lt;br /&gt;&lt;br /&gt;I've really enjoyed using Grails - its a real productivity booster and I really appreciate the &lt;a href="http://en.wikipedia.org/wiki/Separation_of_concerns"&gt;Separation of concerns&lt;/a&gt; you get with an MVC framework. &lt;br /&gt;&lt;br /&gt;I completed the sipX integration first and am now working with &lt;a href="http://msdn.microsoft.com/en-us/library/bb408417.aspx"&gt;Exchange 2007 Web Services&lt;/a&gt; so that users can associate meetings with accounts and mark them billable/non-billable.&lt;br /&gt;&lt;br /&gt;First a few screenshots, then a brief overview of the sipx integration. Note: in the screenshots below the account and contact information is randomly generated test data, while the call records are real records coming out of our production sipX server.&lt;br /&gt;&lt;br /&gt;Call Manager:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_55fIZQMSGNU/SbEfOXdminI/AAAAAAAAAEU/8UsMdSAXqcM/s1600-h/callmanager.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="http://3.bp.blogspot.com/_55fIZQMSGNU/SbEfOXdminI/AAAAAAAAAEU/8UsMdSAXqcM/s320/callmanager.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5310059767323331186" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Account Calls:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_55fIZQMSGNU/SbEfd792QmI/AAAAAAAAAEc/o4yL0ZA9mTM/s1600-h/account-calls.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="http://1.bp.blogspot.com/_55fIZQMSGNU/SbEfd792QmI/AAAAAAAAAEc/o4yL0ZA9mTM/s320/account-calls.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5310060034820293218" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Contact Calls:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_55fIZQMSGNU/SbEfwt9vPqI/AAAAAAAAAEk/SRMqvjf9Rkg/s1600-h/contact-calls.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="http://1.bp.blogspot.com/_55fIZQMSGNU/SbEfwt9vPqI/AAAAAAAAAEk/SRMqvjf9Rkg/s320/contact-calls.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5310060357479251618" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I used the &lt;a href="http://www.grails.org/Quartz+plugin"&gt;Grails Quartz Plugin&lt;/a&gt; and added a grails-app/jobs/CdrSyncJob.groovy that looks at licensees with registered sipX servers and then queries with sipX instance for call detail records that have not yet been processed.&lt;br /&gt;&lt;br /&gt;I wanted call detail report generation to be as fast as possible, so the CdrSyncJob looks up the sipX callee and caller phone numbers against the contact table and licencedUser table then writes a new "call" record into the NetCenter database and marks the sipX call record has having been processed so it can be ignored the next time the job runs. Now whenever anyone wants to view all calls made to any contact within a certain account, its a simple database query that has a few joins and doesn't involve any phone number normalization, determining whether a call is related to any known contact, ignoring interoffice calls, or figuring out the call direction.&lt;br /&gt;&lt;br /&gt;Here a few snippets for CdrSyncJob. First the execute() method:&lt;pre&gt;def execute() {&lt;br /&gt;&lt;br /&gt;  if (Environment.current == Environment.DEVELOPMENT) return&lt;br /&gt;  def licensees = Licensee.withCriteria {&lt;br /&gt;    eq("active", true)&lt;br /&gt;    isNotNull("sipHost")&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  licensees.each { syncCdrs(it); }&lt;br /&gt;}&lt;/pre&gt;Then syncCdrs begins with some Groovy SQL like this:&lt;pre&gt;   def cdr = Sql.newInstance("jdbc:postgresql://${licensee.sipHost}/SIPXCDR", "username", "password", "org.postgresql.Driver")&lt;br /&gt;                                                   cdr.eachRow("select * from view_call_records A, cdrs_sync B where A.id=B.id and NOT(B.done)")&lt;/pre&gt;Hmmm, I guess I should point out that view_call_records and cdrs_sync are custom tables. Here's the SQL:&lt;pre&gt;CREATE VIEW view_call_records as&lt;br /&gt;  select id, SUBSTRING(caller_aor FROM '.*&lt;sip:(.*)@.*&gt;.*') as caller,&lt;br /&gt; LTRIM(LTRIM(SUBSTRING(callee_aor FROM '.*&lt;sip:(.*)@.*&gt;.*'), '8'), '1') as callee,&lt;br /&gt;  connect_time as start_time,&lt;br /&gt; to_char(cdrs.end_time-cdrs.connect_time, 'MI') AS minutes, &lt;br /&gt; to_char(cdrs.end_time-cdrs.connect_time, 'SS') as seconds&lt;br /&gt;  from cdrs where cdrs.termination != 'F' and cdrs.connect_time IS NOT NULL;&lt;br /&gt;&lt;br /&gt;CREATE TABLE cdrs_sync (&lt;br /&gt;    id integer PRIMARY KEY,&lt;br /&gt;    done boolean DEFAULT FALSE&lt;br /&gt;);&lt;/pre&gt;Anyway, the rest of syncCdrs is just about ignoring interoffice calls or calls to contacts with don't have on record, then adding new entries to the NetCenter call table:&lt;pre&gt;new Call(callDirection: direction, callId: it.id, contact: contact, dateStarted: it.start_time, minutes: it.minutes, seconds: it.seconds, licensee: licensee, owner: owner).save();&lt;/pre&gt;and marking the call as processed in the cdrs_sync table.&lt;br /&gt;&lt;br /&gt;Next time I get a chance to blog, I hope to show the Exchange integration and some jQuery snippets. jQuery has been a big productivity booster as well. Web development has come along way!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-9213598122545599124?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/9213598122545599124/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=9213598122545599124' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/9213598122545599124'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/9213598122545599124'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2009/03/netcenter-crm.html' title='NetCenter CRM'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_55fIZQMSGNU/SbEfOXdminI/AAAAAAAAAEU/8UsMdSAXqcM/s72-c/callmanager.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-1510391028188701852</id><published>2009-01-13T10:47:00.003-05:00</published><updated>2009-01-13T11:35:30.694-05:00</updated><title type='text'>Integrating sipX with ejabberd</title><content type='html'>I recently completed integrating our &lt;a href="http://www.sipfoundry.org/"&gt;sipX&lt;/a&gt; based voip platform with our &lt;a href="http://www.ejabberd.im/"&gt;ejabberd&lt;/a&gt; XMPP server, so that users can see when others are on the phone or not. There are alot of similar integrations that people have done with Asterisk using their &lt;a href="http://www.voip-info.org/wiki/view/Asterisk+manager+API"&gt;AMI api&lt;/a&gt;, but I haven't found anything similar for sipX yet, so we rolled our own for now. While, it's not terribly exciting, here's a screenshot of what it looks like when someone is on the phone:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_55fIZQMSGNU/SWy9UGdupPI/AAAAAAAAAEM/1-WD-ofFbvo/s1600-h/phone.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 217px; height: 320px;" src="http://1.bp.blogspot.com/_55fIZQMSGNU/SWy9UGdupPI/AAAAAAAAAEM/1-WD-ofFbvo/s320/phone.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5290811815283827954" /&gt;&lt;/a&gt;&lt;br /&gt;The solution I came up with involves 3 parts. First, I setup a clustered &lt;a href="http://www.rabbitmq.com"&gt;RabbitMQ server&lt;/a&gt; (an open source implementation of &lt;a href="http://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol"&gt;AMQP&lt;/a&gt;). I plan on using it to facilitate a loosely coupled, event driven architecture for integrating multiple open source&lt;br /&gt;applications. I'm pretty happy with RabbitMQ thus far - about the only complaint I have is that they don't have any message tracing capabilities right now (version 1.5.0) which made it more difficult to debug my client side code. I'm also hoping that sometime soon we start seeing debian packages for python/perl amqp libraries. For now, I'm using &lt;a href="http://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol"&gt;Net::Stomp&lt;/a&gt; and the RabbitMQ stomp adapter which seemed like the most stable, easily deployed client side solution.&lt;br /&gt;&lt;br /&gt;On the XMPP server side, I created an erlang module that acts as a message consumer. Each virtual host in our ejabberd server listens on a separate queue for presence messages generated by the sipX side and sends out XMPP presence updates to online sessions.&lt;br /&gt;&lt;br /&gt;After getting the RabbitMQ erlang client library installed, here's the code I used to connect and setup my consumer:&lt;pre&gt;Connection = amqp_connection:start(Uname, Pwd, "mq.nvizn.com"),&lt;br /&gt;Channel = amqp_connection:open_channel(Connection),                                &lt;br /&gt;Qname = list_to_binary("/" ++ Host ++ "/presence/phone"),&lt;br /&gt;Q = lib_amqp:declare_queue(Channel, Qname),&lt;br /&gt;lib_amqp:bind_queue(Channel, &lt;&lt;""&gt;&gt;, Q, Qname),&lt;br /&gt;lib_amqp:subscribe(Channel, Q, self(), false),&lt;br /&gt;&lt;br /&gt;Then I created a handle_info function that looks like this:&lt;br /&gt;&lt;br /&gt;handle_info({ {'basic.deliver', DeliveryTag, _, _, _, _ },&lt;br /&gt;              {content, ClassId, Properties, PropertiesBin,&lt;br /&gt;              [Payload]} = Info}, State) -&gt;&lt;br /&gt;&lt;br /&gt;%% Message processing here, then send out the XMPP presence update...,&lt;br /&gt;BroadcastPresence = fun({U, S, R}) -&gt;&lt;br /&gt;    Dest = jlib:make_jid(U, S, R),&lt;br /&gt;    ejabberd_router:route(FromJID, Dest, Presence)&lt;br /&gt;  end,                                                                             &lt;br /&gt;Sessions = ejabberd_sm:get_vh_session_list(State#state.host),&lt;br /&gt;lists:foreach(BroadcastPresence, Sessions),&lt;/pre&gt;Now on the sipX side, things are a bit more ugly, and when I have more time later, I'd like to rework this end. For now, I created a PL/pgSQL AFTER trigger on &lt;a href="http://sipx-wiki.calivia.com/index.php/Call_Detail_Records_%28CDR%29"&gt;SIPXCDR.call_state_events table&lt;/a&gt; that handles new call state events ('S' and 'E' event_types to be specific). This trigger inserts new rows into a new cse_summary table I created for every call, one for when the call is setup and one for call termination and it does this for each internal user. If the call involves two internal folks, you end up with 4 rows, if on the other hand, one side is external, you end up with only 2 rows. This trigger also looks up the XMPP jid for the extension and records that in the generated cse_summary rows.              &lt;br /&gt;&lt;br /&gt;When a row is created in the cse_summary table, a separate&lt;br /&gt;PL/Perl AFTER trigger uses Net::Stomp to generate a call state&lt;br /&gt;event message for the RabbitMQ cluster.&lt;br /&gt;&lt;br /&gt;Here's what the PL/Perl trigger looks like:&lt;pre&gt;my $stomp = Net::Stomp-&gt;new({hostname=&gt;'mq.nvizn.com',port=&gt;'61613'});&lt;br /&gt;$stomp-&gt;connect({login=&gt;$uid, passcode=&gt;$pwd});&lt;br /&gt;&lt;br /&gt;my $msg = sprintf("%s,%s,%s", $domain,                                             &lt;br /&gt;$_TD-&gt;{"new"}{"event_type"}, $_TD-&gt;{"new"}{"jid"});&lt;br /&gt;&lt;br /&gt;$stomp-&gt;send({destination=&gt;"/$domain/presence/phone", body=&gt;($msg)});&lt;br /&gt;$stomp-&gt;disconnect;&lt;/pre&gt;Now, I'm just creating some debian packages and RPMs (for the sipX side), documenting how it works, and thinking about our next integration.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-1510391028188701852?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/1510391028188701852/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=1510391028188701852' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1510391028188701852'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1510391028188701852'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2009/01/integrating-sipx-with-ejabberd.html' title='Integrating sipX with ejabberd'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_55fIZQMSGNU/SWy9UGdupPI/AAAAAAAAAEM/1-WD-ofFbvo/s72-c/phone.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-8858463325078624957</id><published>2008-12-06T14:59:00.005-05:00</published><updated>2008-12-06T15:39:59.614-05:00</updated><title type='text'>Load Balance Clustered Ejabberd Servers</title><content type='html'>I recently completed setting up our XMPP infrastructure. After spending some time reviewing the current capabilities of jabberd2, openfire, djabberd, and ejabberd, I decided that ejabberd had the &lt;a href="http://www.ejabberd.im/features"&gt;best combination of features&lt;/a&gt; for our needs: virtual hosting, LDAP integration, clustering support, shared rosters, and reasonably &lt;a href="http://www.process-one.net/en/ejabberd/guide_en"&gt;good documentation&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;So after setting up the first ejabberd node (im1), with a test virtual host and working LDAP integration, I setup our second ejabberd node (im2) by copying &lt;i&gt;/etc/ejabberd/ejabberd.cfg&lt;/i&gt; to the 2nd node, then running through the following steps:&lt;br /&gt;&lt;br /&gt;&lt;li&gt;First launch an erlang shell &lt;b&gt;as the ejabberd user&lt;/b&gt;, with &lt;i&gt;erl -sname ejabberd@im2 -mnesia extra_db_nodes "['ejabberd@im1']" -s mnesia&lt;/i&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Then, to replicate all ejabberd tables in my configuration, I ran a: &lt;i&gt;mnesia:change_table_copy_type(schema, node(), disc_copies).mnesia:add_table_copy(offline_msg,node(),disc_only_copies). mnesia:add_table_copy(privacy,node(),disc_copies). mnesia:add_table_copy(sr_group,node(),disc_copies). mnesia:add_table_copy(sr_user,node(),disc_copies). mnesia:add_table_copy(roster,node(),disc_copies). mnesia:add_table_copy(last_activity,node(),disc_copies). mnesia:add_table_copy(disco_publish,node(),disc_only_copies). mnesia:add_table_copy(pubsub_node,node(),disc_copies). mnesia:add_table_copy(pubsub_state,node(),disc_copies). mnesia:add_table_copy(pubsub_item,node(),disc_only_copies). mnesia:add_table_copy(session,node(),ram_copies). mnesia:add_table_copy(s2s,node(),ram_copies). mnesia:add_table_copy(route,node(),ram_copies). mnesia:add_table_copy(iq_response,node(),ram_copies). mnesia:add_table_copy(caps_features,node(),ram_copies). mnesia:add_table_copy(motd_users,node(),disc_copies). mnesia:add_table_copy(motd,node(),disc_copies). mnesia:add_table_copy(acl,node(),disc_copies). mnesia:add_table_copy(config,node(),disc_copies).&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;After you quit the shell, you'll most likely need to move the result mnesia database files to the ejabberd user's $HOME folder.&lt;br /&gt;&lt;br /&gt;Once, both nodes were working correctly I setup a &lt;a href="http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.LVS-DR.html"&gt;LVS-DR&lt;/a&gt; load balancer with &lt;a href="http://www.vergenet.net/linux/ldirectord/"&gt;ldirectord&lt;/a&gt;. This proves to be rather straightforward.&lt;br /&gt;&lt;br /&gt;First the realservers (each ejabberd instance, im1 and im2) had to configured with a local interface that listens to the load balancer's VIP (virtual IP). The most reliable way I found to set this up was with a simple &lt;pre&gt;ip addr add 172.16.254.60/32 brd + dev lo label lo:vip&lt;/pre&gt; in &lt;i&gt;/etc/rc.local&lt;/i&gt;. &lt;br /&gt;&lt;br /&gt;Then I setup a &lt;i&gt;/etc/sysctl.d/60-ipvs-arp-rules.conf&lt;/i&gt; with &lt;pre&gt;net.ipv4.conf.eth0.arp_ignore = 1&lt;br /&gt;net.ipv4.conf.eth0.arp_announce = 2&lt;br /&gt;net.ipv4.conf.all.arp_ignore = 1&lt;br /&gt;net.ipv4.conf.all.arp_announce = 2&lt;/pre&gt;On Ubuntu (and I think debian as well), you must also tweak &lt;i&gt;/etc/sysctl.d/10-network-security.conf&lt;/i&gt; to &lt;a href="http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.LVS-DR.html#set_rp_filter"&gt;disable source address validation&lt;/a&gt;&lt;pre&gt;net.ipv4.conf.default.rp_filter=0&lt;br /&gt;net.ipv4.conf.all.rp_filter=0&lt;/pre&gt; That's pretty much it for the realservers.&lt;br /&gt;&lt;br /&gt;Setting up the loadbalancer involves setting up the VIP in &lt;i&gt;/etc/network/interfaces&lt;/i&gt;&lt;pre&gt;auto eth0:vip0&lt;br /&gt;iface eth0:vip0 inet static&lt;br /&gt;  address 172.16.254.60&lt;br /&gt;  broadcast 172.16.254.60&lt;br /&gt;  netmask 255.255.255.255&lt;/pre&gt;Then setting up ldirectord (apt-get install ldirectord) in /etc/ldirectord.cf with &lt;pre&gt;/etc/ldirectord.cf&lt;br /&gt;# Global Directives&lt;br /&gt;checktimeout=3&lt;br /&gt;checkinterval=15&lt;br /&gt;autoreload=yes&lt;br /&gt;logfile="/var/log/ldirectord.log"&lt;br /&gt;logfile="local0"&lt;br /&gt;emailalert="joel.reed@nvizn.com"&lt;br /&gt;emailalertfreq=3600&lt;br /&gt;emailalertstatus=all&lt;br /&gt;quiescent=yes&lt;br /&gt;&lt;br /&gt;virtual=172.16.254.60:5222&lt;br /&gt;    real=172.16.254.70:5222 gate&lt;br /&gt;    real=172.16.254.72:5222 gate&lt;br /&gt;    scheduler=wlc&lt;br /&gt;    protocol=tcp&lt;br /&gt;    checktype=negotiate&lt;br /&gt;    service=simpletcp&lt;br /&gt;    request="junk"&lt;br /&gt;    receive="jabber.org"&lt;/pre&gt;It'd be really cool if there was some kind of builtin heathcheck call you could do on an ejabberd node, but alas there isn't so I just send it a string of garbage ("junk" to be exact), and look for the jabber.org string in the XMPP response. Seems to be working OK thus far...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-8858463325078624957?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/8858463325078624957/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=8858463325078624957' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8858463325078624957'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8858463325078624957'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2008/12/load-balance-clustered-ejabberd-servers.html' title='Load Balance Clustered Ejabberd Servers'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7250070533247500900</id><published>2008-11-03T10:02:00.006-05:00</published><updated>2008-11-03T10:29:47.762-05:00</updated><title type='text'>Alfresco on EC2</title><content type='html'>Over the weekend, I created a &lt;a href="http://wiki.alfresco.com/wiki/Alfresco_Labs_3"&gt;Alfresco Labs 3b&lt;/a&gt; AMI on &lt;a href="http://aws.amazon.com/ec2/"&gt;EC2&lt;/a&gt;, Amazon's cloud computing platform.&lt;br /&gt;&lt;br /&gt;I took one of the &lt;a href="http://alestic.com/"&gt;Alestic&lt;/a&gt; Ubuntu 8.10 base images, added my own ec2-tools_0.1.deb package, and built out an AMI with Labs 3b running on the system tomcat5.5, instead of the bundled tomcat instance. That part was far more brutal than using EC2. You have to make quiet a few changes to the catalina policy to get things working.&lt;br /&gt;&lt;br /&gt;I made an Alfresco package, that installs an /etc/tomcat5.5/policy.d/60alfresco.policy file that looks like this:&lt;pre&gt;grant { &lt;br /&gt;    permission java.lang.RuntimePermission "accessClassInPackage.org.apache.*";&lt;br /&gt;&lt;br /&gt;    permission java.lang.RuntimePermission "accessDeclaredMembers";    &lt;br /&gt;    permission java.lang.reflect.ReflectPermission "suppressAccessChecks";&lt;br /&gt;    permission java.util.PropertyPermission "alfresco.jmx.dir", "read,write";&lt;br /&gt;    permission java.util.PropertyPermission "webapp.root", "read,write";&lt;br /&gt;    permission java.io.FilePermission "/usr/share/java/servlet-api-2.4.jar", "read";&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;grant codeBase "file:${catalina.home}/bin/tomcat-juli.jar" {&lt;br /&gt;    permission java.io.FilePermission "/usr/share/tomcat5.5/webapps/alfresco/WEB-INF/classes/logging.properties", "read";&lt;br /&gt;    permission java.io.FilePermission "/var/lib/tomcat5.5/temp/-", "read,write,delete,execute";&lt;br /&gt;    permission java.io.FilePermission "/var/lib/tomcat5.5/temp", "read,write,execute";&lt;br /&gt;}&lt;/pre&gt;All of my AMIs have a rebundle.sh script that can quickly upload an updated AMI. It looks something like this:&lt;pre&gt;#!/bin/sh&lt;br /&gt;ACCOUNTID=xxxxxx&lt;br /&gt;CERTFILE=/etc/ec2/xxxxxxx.pem&lt;br /&gt;KEYFILE=/etc/ec2/xxxxxxx.pem&lt;br /&gt;ACCESSKEY=xxxxxxxxxxx&lt;br /&gt;SECRETKEY=xxxxxxxxxx&lt;br /&gt;&lt;br /&gt;umount /var/local&lt;br /&gt;ec2-bundle-vol -u $ACCOUNTID -c $CERTFILE -k $KEYFILE -p ubuntu-8.10-appsuite-1.0-20081101 --ec2cert /etc/ec2/amitools/cert-ec2.pem -r i386 &lt;br /&gt;ec2-upload-bundle -b nvizn.com -m /tmp/ubuntu-8.10-appsuite-1.0-20081101.manifest.xml -a $ACCESSKEY -s $SECRETKEY&lt;br /&gt;ec2-register nvizn.com/ubuntu-8.10-appsuite-1.0-20081101.manifest.xml&lt;/pre&gt;This made life a bit easier as I made changes to the image and uploaded them. I unmount /var/local at the start of the script as that's where I mount my EBS volume.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7250070533247500900?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7250070533247500900/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7250070533247500900' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7250070533247500900'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7250070533247500900'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2008/11/alfresco-on-ec2.html' title='Alfresco on EC2'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-66473336966768828</id><published>2008-10-20T21:11:00.004-04:00</published><updated>2008-10-20T21:29:08.889-04:00</updated><title type='text'>Samba4 on Ubuntu Intrepid</title><content type='html'>Here's a brief rundown of my experiences with &lt;a href="http://wiki.samba.org/index.php/Samba4"&gt;Samba4&lt;/a&gt; on Ubuntu Intrepid.&lt;br /&gt;&lt;br /&gt;I first tried the samba4 package in the ubuntu intrepid repositories, but when you do a &lt;pre&gt;./setup/provision --realm=azulogic.com --domain=azulogic --adminpass=fubar --server-role='domain controller'&lt;/pre&gt; you get a python stackdump with&lt;pre&gt;IOError: [Errno 2] No such file or directory: '/usr/etc/samba/smb.conf'&lt;br /&gt;&lt;/pre&gt;I tried creating a "/usr/etc/samba" folder (though the distaste was high), but then proceeded to get further file path errors.&lt;br /&gt;&lt;br /&gt;So, next I switched to the &lt;a href="http://packages.debian.org/source/experimental/samba4"&gt;Debian Experimental&lt;/a&gt; package. This worked much better.&lt;br /&gt;&lt;br /&gt;After you apt-get install the package, you'll have to fixup /etc/init.d/samba4 - it's still looking for smbd (the samba3 daemon), whereas in samba4 its now /usr/sbin/samba.&lt;br /&gt;&lt;br /&gt;So, I just did a &lt;pre&gt;ln -s /usr/sbin/samba /usr/sbin/smbd&lt;/pre&gt;to get it to work.&lt;br /&gt;&lt;br /&gt;After getting krb5, dns, and samba ready to go, I tried to join a linux machine running winbind 2:3.2.3-1ubuntu3 to the domain. No luck though: &lt;pre&gt;(~) net ads join -U Administrator&lt;br /&gt;Enter Administrator's password:&lt;br /&gt;Failed to join domain: failed to lookup DC info for domain 'AZULOGIC.COM' over rpc: NT_STATUS_INTERNAL_ERROR&lt;/pre&gt;How do you fix this? One way is to run in the "single" process model mode. I changed /etc/init.d/samba4 to launch the samba daemon with -M single. Then you see a nice:&lt;pre&gt;(~) net ads join -U Administrator&lt;br /&gt;Enter Administrator's password:&lt;br /&gt;Using short domain name -- AZULOGIC&lt;br /&gt;Joined 'LTS' to realm 'azulogic.com&lt;/pre&gt;One final note: as far as I can tell the debian version (4.0.0alpha6-GIT-7fb9007) crashes when someone tries to do a change password. So beware!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-66473336966768828?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/66473336966768828/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=66473336966768828' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/66473336966768828'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/66473336966768828'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2008/10/samba4-on-ubuntu-intrepid.html' title='Samba4 on Ubuntu Intrepid'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-394696306651116856</id><published>2008-10-16T15:11:00.008-04:00</published><updated>2008-10-16T15:39:15.153-04:00</updated><title type='text'>Secure Apt Repository Howto</title><content type='html'>After a good bit of googling and poking around, I completed the setup of our secure apt repository here at &lt;span style="font-weight: bold;"&gt;nvizn&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Here's how you'd do it for an Ubuntu intrepid repository.&lt;br /&gt;&lt;br /&gt;First, setup a directory tree that looks like this:&lt;pre&gt;mkdir -p /var/www/packages/dists/intrepid/main/binary-i386/&lt;br /&gt;mkdir -p /var/www/packages/intrepid/main&lt;/pre&gt;Then, install apt-ftparchive, which will do most of the heavy lifting.&lt;pre&gt;apt-get install apt-ftparchive&lt;/pre&gt;Now, drop all your .debs into /var/www/packages/intrepid/main/ and create an apt-ftparchive configuration file at &lt;span style="font-style: italic;"&gt;/etc/archive.config&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here's what mine looks like:&lt;pre&gt;Dir {&lt;br /&gt;  ArchiveDir "/var/www/packages";&lt;br /&gt;  CacheDir "/home/joel.reed/uploads/";&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;Default {&lt;br /&gt;  Packages::Compress ". gzip bzip2";&lt;br /&gt;  Sources::Compress ". gzip bzip2";&lt;br /&gt;  Contents::Compress ". gzip bzip2";&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;APT::FTPArchive::Release::Codename "intrepid";&lt;br /&gt;APT::FTPArchive::Release::Suite "intrepid";&lt;br /&gt;APT::FTPArchive::Release::Origin "Joel W. Reed";&lt;br /&gt;&lt;br /&gt;TreeDefault {&lt;br /&gt;  BinCacheDB "packages-$(SECTION)-$(ARCH).db";&lt;br /&gt;  Directory "intrepid/$(SECTION)";&lt;br /&gt;  Packages "$(DIST)/$(SECTION)/binary-$(ARCH)/Packages";&lt;br /&gt;  SrcDirectory "intrepid/$(SECTION)";&lt;br /&gt;  Sources "$(DIST)/$(SECTION)/source/Sources";&lt;br /&gt;  Contents "$(DIST)/Contents-$(ARCH)";&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;Tree "dists/intrepid" {&lt;br /&gt;   Sections "main";&lt;br /&gt;   Architectures "i386";&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Finally, run this sequence of commands:&lt;br /&gt;&lt;pre&gt;apt-ftparchive generate /etc/archive.config&lt;br /&gt;cd /var/www/packages/dists/intrepid/&lt;br /&gt;apt-ftparchive -c /etc/archive.config release . &gt; Release&lt;br /&gt;rm -v Release.gpg&lt;br /&gt;gpg -v --output Release.gpg -ba Release&lt;br /&gt;&lt;/pre&gt;When you're done, you'll end up with a /var/www/packages tree that looks something like this:&lt;pre&gt;/var/www/packages/dists/intrepid&lt;br /&gt;/var/www/packages/dists/intrepid/main&lt;br /&gt;/var/www/packages/dists/intrepid/main/binary-i386&lt;br /&gt;/var/www/packages/dists/intrepid/main/binary-i386/Packages.gz&lt;br /&gt;/var/www/packages/dists/intrepid/main/binary-i386/Packages.bz2&lt;br /&gt;/var/www/packages/dists/intrepid/main/binary-i386/Packages&lt;br /&gt;/var/www/packages/dists/intrepid/Contents-i386&lt;br /&gt;/var/www/packages/dists/intrepid/Release&lt;br /&gt;/var/www/packages/dists/intrepid/Release.gpg&lt;br /&gt;/var/www/packages/dists/intrepid/Contents-i386.gz&lt;br /&gt;/var/www/packages/dists/intrepid/Contents-i386.bz2&lt;br /&gt;/var/www/packages/intrepid&lt;br /&gt;/var/www/packages/intrepid/main&lt;br /&gt;/var/www/packages/intrepid/main/alfresco-r3184-0.3.1.deb&lt;br /&gt;/var/www/packages/intrepid/main/nvizn-base-0.3.6.deb&lt;br /&gt;/var/www/packages/intrepid/main/libnss-cache_0.1-1_i386.deb&lt;br /&gt;/var/www/packages/intrepid/main/nsscache_0.8.4.1_all.deb&lt;br /&gt;/var/www/packages/intrepid/main/stratus-desktop-0.2.deb&lt;br /&gt;/var/www/packages/intrepid/main/packages-main-i386.db&lt;br /&gt;/var/www/packages/intrepid/main/jsetup_0.5.1_all.deb&lt;br /&gt;&lt;/pre&gt;Now, to make all this work, you need to have a gpg key of course, and apache set to serve up /var/www/packages, and all client machines need the public key. To do that with a key on a keyserver, do something like &lt;br /&gt;&lt;pre&gt;gpg --recv-keys B1850655 &amp;&amp; gpg --export B1850655 | apt-key add -&lt;br /&gt;&lt;/pre&gt;Hope this is helpful to you!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-394696306651116856?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/394696306651116856/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=394696306651116856' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/394696306651116856'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/394696306651116856'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2008/10/secure-apt-repository-howto.html' title='Secure Apt Repository Howto'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7511005781800490099</id><published>2008-10-13T22:16:00.004-04:00</published><updated>2008-10-13T22:27:04.695-04:00</updated><title type='text'>Startup</title><content type='html'>I haven't blogged for while, because I've been putting a lot of hours into an open source startup company. It's been great fun to work with some new technologies like &lt;a href="http://groovy.codehaus.org/"&gt;Groovy&lt;/a&gt;, &lt;a href="http://grails.org/"&gt;Grails&lt;/a&gt;, &lt;a href="http://couchdb.org/"&gt;CouchDB&lt;/a&gt;, and &lt;a href="http://wiki.samba.org/index.php/Samba4"&gt;Samba4&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Among other things, I setup an openldap server, built a &lt;a href="http://www.openldap.org/lists/openldap-software/200809/msg00078.html"&gt;few&lt;/a&gt; &lt;a href="http://www.openldap.org/lists/openldap-software/200806/msg00029.html"&gt;custom&lt;/a&gt; &lt;a href=""&gt;www.openldap.org/lists/openldap-software/200807/msg00002.html&lt;/cite&gt;"&gt;overlays&lt;/a&gt;, and integrated Zimbra, Alfresco, Openfire, &lt;a href="http://www.blogger.com/www.sipfoundry.org"&gt;SipX&lt;/a&gt;, Samba3, and an Ubuntu desktop. Each of these integrations has there pros and cons, perhaps Zimbra and SipX are the nicest.&lt;br /&gt;&lt;br /&gt;I'm hoping to blog about my experience with Samba4 shortly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7511005781800490099?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7511005781800490099/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7511005781800490099' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7511005781800490099'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7511005781800490099'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2008/10/startup.html' title='Startup'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-4277471467933298966</id><published>2008-02-04T17:03:00.000-05:00</published><updated>2008-02-04T17:20:01.971-05:00</updated><title type='text'>OpenTF 0.6.0 Release</title><content type='html'>Wow - two months without a blog post and 3 months since my last OpenTF release! For the last month or two, I really haven't worked much on &lt;a href="http://code.google.com/p/opentf/"&gt;OpenTF&lt;/a&gt;, preferring instead to work on learning NT Greek and more about the &lt;a href="http://www.amazon.com/Biblical-Commentary-Isaiah-watts-513pp/dp/0849902231"&gt;Book of Isaiah&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The latest release includes a few new goodies and many bugfixes. There's the new IRC &lt;a href="http://code.google.com/p/opentf/wiki/TfsBot"&gt;changeset notification bot&lt;/a&gt;, support for &lt;a href="http://cruisecontrol.sourceforge.net/"&gt;CruiseControl&lt;/a&gt; (an open source continuous build framework), a &lt;a href="http://flickr.com/photos/11527903@N04/2231467443/"&gt;monodevelop plugin for browsing TFS servers&lt;/a&gt;, and several new commands like "shelve", "rollback", and "merges".&lt;br /&gt;&lt;br /&gt;Over the next few month, I hope to be able to further develop the monodevelop plugin, continue work on &lt;a href="http://code.google.com/p/opentf/wiki/ClientDifferences"&gt;missing commands&lt;/a&gt;, and begin testing other open source Team Foundation tools for compatibility with the OpenTF libraries.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-4277471467933298966?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/4277471467933298966/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=4277471467933298966' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/4277471467933298966'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/4277471467933298966'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2008/02/opentf-060-release.html' title='OpenTF 0.6.0 Release'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-8614194416293306410</id><published>2007-11-30T13:03:00.000-05:00</published><updated>2007-11-30T13:21:45.329-05:00</updated><title type='text'>Job Openings</title><content type='html'>If there's anyone looking for a ASP.Net developer position in the Pittsburgh (PA) area and you've contributed to the mono project in the past, please put a link to your resume in the comments for this post. We're a great company to work and are early adopters of .Net related technologies. I'd love to be able to hire folks who have helped out the mono project. Thanks!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-8614194416293306410?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/8614194416293306410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=8614194416293306410' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8614194416293306410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8614194416293306410'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/11/job-openings.html' title='Job Openings'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7395392457834016883</id><published>2007-11-08T23:08:00.000-05:00</published><updated>2007-11-08T23:25:01.041-05:00</updated><title type='text'>MonoDevelop and Team Foundation</title><content type='html'>Now that &lt;a href="http://www.monodevelop.com/"&gt;MonoDevelop&lt;/a&gt; is nearing a 1.0 release, I thought I'd take another look at fleshing out a TeamFoundation plugin for MD.&lt;br /&gt;&lt;br /&gt;For starters, I'm taking the "&lt;a href="http://ropeonfire.blogspot.com/2007/08/explore-command.html"&gt;tf explore&lt;/a&gt;" command in &lt;a href="http://code.google.com/p/opentf/"&gt;OpenTF&lt;/a&gt;, factoring out the Gtk classes into a separate assembly, then building out an MD addin that makes use of it.&lt;br /&gt;&lt;br /&gt;Obligatory screenshot: &lt;a href="http://www.flickr.com/photos/11527903@N04/1927985084/" title="Photo Sharing"&gt;&lt;img src="http://farm3.static.flickr.com/2101/1927985084_7ca8e9a06d_b.jpg" width="1024" height="640" alt="OpenTF-MonoDevelop-v1" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It will take a while to clean up this code and the build machinery, and to figure out how to make better use of builtin MonoDevelop addin services, but perhaps in a release of two, we'll have something useful. If you're interested in helping, please do!&lt;br /&gt;&lt;br /&gt;At some point, I'm also hopeful that those developing the VersionControl API for MD can consider the needs of a Team Foundation plugin. I'd be very interested in seeing if we can make something work for SVN, GIT, etc. and TFS. That'd be a much better situation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7395392457834016883?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7395392457834016883/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7395392457834016883' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7395392457834016883'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7395392457834016883'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/11/monodevelop-and-team-foundation.html' title='MonoDevelop and Team Foundation'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm3.static.flickr.com/2101/1927985084_7ca8e9a06d_t.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-3405036504626924884</id><published>2007-10-29T20:34:00.001-04:00</published><updated>2007-10-29T21:09:36.894-04:00</updated><title type='text'>OpenTF Build Changes</title><content type='html'>I started the &lt;a href="http://code.google.com/p/opentf/"&gt;OpenTF project&lt;/a&gt; out by copying the &lt;a href="http://www.mono-project.com/Olive"&gt;Mono Olive&lt;/a&gt; tree, and replacing its assemblies and tools with my Team Foundation files. This worked well on *nix, but recently I've been trying to improve support for building on Windows as well.&lt;br /&gt;&lt;br /&gt;Should I use &lt;b&gt;cscript, nmake, powershell, BAT, project files&lt;/b&gt;, or some combination of these? How could I implement a build solution that didn't just duplicate the same build instructions (source files, references, etc) in 2 different formats: one for windows and one for *nix?&lt;br /&gt;&lt;br /&gt;I decided to keep things simple on Windows - just use a &lt;b&gt;VS2005 solution&lt;/b&gt; with a bunch of project files. Then for *nix, I decided to make libxslt's &lt;a href="http://xmlsoft.org/XSLT/xsltproc2.html"&gt;xsltproc&lt;/a&gt; a build requirement, and generate the list of sources and references for the mono olive make machinery using a few simple &lt;b&gt;XSL stylesheets&lt;/b&gt;. &lt;br /&gt;&lt;br /&gt;For example, all the .sources files are now generated via build/sources.xsl. Which looks something like this:&lt;pre&gt;  &amp;lt;xsl:template match="/"&amp;gt;&lt;br /&gt;    &amp;lt;xsl:apply-templates select="Project/ItemGroup/Compile"/&amp;gt;&lt;br /&gt;  &amp;lt;/xsl:template&amp;gt;&lt;br /&gt;&lt;br /&gt;  &amp;lt;xsl:template match="Compile"&amp;gt;&lt;br /&gt;    &amp;lt;xsl:value-of select="@Include" /&amp;gt;&amp;lt;xsl:text&amp;gt; &amp;lt;/xsl:text&amp;gt;&lt;br /&gt;  &amp;lt;/xsl:template&amp;gt;&lt;/pre&gt;I also have .references files for each assembly, also generated via an XSL file from the .csproj.&lt;br /&gt;&lt;br /&gt;Now, I just maintain the VS2005 project files, and leave the *nix build stuff to the stylesheets. I added support for conditional sources using the &lt;a href="http://msdn2.microsoft.com/en-us/library/ms164283(VS.80).aspx"&gt;Conditional attribute&lt;/a&gt;. Its working quite well thus far.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-3405036504626924884?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/3405036504626924884/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=3405036504626924884' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3405036504626924884'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3405036504626924884'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/10/opentf-build-changes.html' title='OpenTF Build Changes'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-1449467023096881857</id><published>2007-10-02T20:36:00.000-04:00</published><updated>2007-10-02T21:20:12.526-04:00</updated><title type='text'>Using git-svn with Mono</title><content type='html'>&lt;span style="font-weight:bold;"&gt;Why use git to hack on mono?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I've found myself far more productive and make heavy use of &lt;a href="http://utsl.gen.nz/talks/git-svn/intro.html#feature-branches"&gt;feature branches&lt;/a&gt; and &lt;a href="http://www.kernel.org/pub/software/scm/git/docs/v1.5.3/git-pull.html"&gt;squashed commits&lt;/a&gt; for my day job, so when I hack on mono, I really enjoy being able to leverage the same capabilities.&lt;br /&gt;&lt;br /&gt;By "squashed commits", I guess I should really say, leveraging the power of a &lt;a href="http://en.wikipedia.org/wiki/Revision_control#Distributed_revision_control"&gt;distributed version control system&lt;/a&gt; that lets me break down a task into many smaller steps, commit each step individually, then squashing the whole thing down to one patch that I can post to &lt;a href="http://lists.ximian.com/mailman/listinfo/mono-devel-list"&gt;mono-devel&lt;/a&gt; for review.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;So do you set things up to use git with Mono?&lt;/span&gt;&lt;pre&gt;cd /usr/local/src/&lt;br /&gt;mkdir mono &amp;&amp; cd mono&lt;br /&gt;mkdir mcs &amp;&amp; cd mcs&lt;br /&gt;git-svn init svn+ssh://username@mono-cvs.ximian.com/source/trunk/mcs&lt;br /&gt;git-svn fetch -r 86200 &amp;&amp; git-svn fetch&lt;br /&gt;cd ..&lt;br /&gt;mkdir mono &amp;&amp; cd mono&lt;br /&gt;git-svn init svn+ssh://username@mono-cvs.ximian.com/source/trunk/mono&lt;br /&gt;git-svn fetch -r 86200 &amp;&amp; git-svn fetch&lt;br /&gt;&lt;/pre&gt;Note: the above recipe copies the svn history only back to revision 86200. You can pick any valid svn revision number you like, or if you want the full revision history see &lt;a href="http://www.mono-project.com/GitSVN"&gt; this page on the Mono wiki&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Ok. Everything's setup. Now what?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First, let's say I want to hack on some ASP.NET ashx page bug. I'll setup a local branch "ashx" to store whatever code I write/change:&lt;pre&gt;git-checkout -b ashx&lt;/pre&gt;Now I have two branches: "master" which was setup by git-svn above, and "ashx" which I just created and switched over to. Now, I can:&lt;br /&gt;&lt;pre&gt;emacs -nw class/System.Web/...&lt;br /&gt;git-commit -a -m "1st step"&lt;br /&gt;emacs -nw class/System.Web/...&lt;br /&gt;git-commit -a -m "2nd step"&lt;br /&gt;emacs -nw class/System.Web/...&lt;br /&gt;git-commit -a -m "3rd step"&lt;br /&gt;&lt;/pre&gt;Ok, now to post a message to mono-devel:&lt;br /&gt;&lt;pre&gt;git-diff master ashx &gt; ~/Bug6884.fix&lt;br /&gt;mutt&lt;/pre&gt;When everything looks good and no one has any complaints, I can finally commit back to mono's svn repository with:&lt;pre&gt;git-branch master&lt;br /&gt;git-pull --squash --summary . ashx&lt;br /&gt;git-commit -a -m "message for mono's svn"&lt;br /&gt;git-svn dcommit&lt;/pre&gt;This "squashes" my commits down into one batch of changes on the master branch, which I then commit and push to svn repo.&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;&lt;br /&gt;Finally, How do I update my local tree?&lt;/span&gt;&lt;pre&gt;git-svn fetch &amp;&amp; git-svn rebase remotes/git-svn&lt;/pre&gt;Note: This will update the current branch you are on locally.&lt;br /&gt;&lt;br /&gt;Hope someone finds this helpful!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-1449467023096881857?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/1449467023096881857/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=1449467023096881857' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1449467023096881857'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1449467023096881857'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/10/using-git-svn-with-mono.html' title='Using git-svn with Mono'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7839218919827940568</id><published>2007-09-20T23:31:00.000-04:00</published><updated>2007-09-20T23:52:25.953-04:00</updated><title type='text'>test RPM for tf4mono</title><content type='html'>I just caught up on my reading of the mono mailing list and saw Miguel's post about &lt;a href="http://lists.ximian.com/pipermail/mono-list/2007-September/036266.html"&gt;Mono Packaged .NET apps for Mono&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Since I have debian packages and a win32 installer for &lt;a href="http://code.google.com/p/tf4mono/"&gt;tf4mono&lt;/a&gt;, I thought it might be time to make an RPM package as well and maybe help this QA effort.&lt;br /&gt;&lt;br /&gt;Anyway, I downloaded the very helpful Mono 1.2.5 VMWare image and went to work on creating a spec file for rpmbuild. Side note: cleaning out the bash history and ~/.ssh might be a sensible improvement to this image.&lt;br /&gt;&lt;br /&gt;I had to try and remember all the old rpm command line options I used to use in my sleep - as I fell in love with debian's apt-get several years ago and forgot most rpm incantations.&lt;br /&gt;&lt;br /&gt;Anyway, here's the &lt;a href="http://tf4mono.googlecode.com/files/tf4mono-0.5.2-0.i586.rpm"&gt;resultant RPM package&lt;/a&gt;. By the way, I enabled the optional gtksourceview-sharp based syntax highlighting in the package.&lt;br /&gt;&lt;br /&gt;If anyone can review the &lt;a href="http://repo.or.cz/w/tfs.git?a=blob_plain;f=tfs.spec.in;h=0bc5ab2d27127a2846ed8c09818c620b8e8ed3b3"&gt;tfs.spec.in file&lt;/a&gt; or the &lt;a href="http://tf4mono.googlecode.com/files/tf4mono-0.5.2-0.i586.rpm"&gt;RPM file&lt;/a&gt; and offer suggestions for improvement, please do so. I'll gladly make a necessary cleanups.&lt;br /&gt;&lt;br /&gt;I installed the package and ran "tf show build" and "tf show stats /server:my.tfs.server.ip" and "tf explore /server:my.tfs.server.ip" and everything seemed in order. The "tf show" commands are new in the soon to be released 0.5.2 version of tf4mono.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7839218919827940568?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7839218919827940568/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7839218919827940568' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7839218919827940568'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7839218919827940568'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/09/test-rpm-for-tf4mono.html' title='test RPM for tf4mono'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7725362939708178429</id><published>2007-09-07T23:11:00.000-04:00</published><updated>2007-09-07T23:21:14.662-04:00</updated><title type='text'>Monthly Sleep Deprivation</title><content type='html'>Oddly enough, I've seem to have fallen into a schedule of releasing updates&lt;br /&gt;to tf4mono about once a month. This month's release is &lt;a href="http://lists.ximian.com/pipermail/mono-list/2007-September/036181.html"&gt;&lt;br /&gt;tf4mono 0.5.1&lt;/a&gt; which includes win32 installation packages, a GTK-based gui mode&lt;br /&gt;for exploring TFS repositories, many command enhancements, improved builtin help with&lt;br /&gt;usage guidelines, and numerous bugfixes.&lt;br /&gt;&lt;br /&gt;I always need a bit of downtime after a release first of course -&lt;br /&gt;working on open source software as a hobby is fun,&lt;br /&gt;but always ends up meaning lost sleep every so often.&lt;br /&gt;&lt;br /&gt;Anyway, I'm interested in hearing what features would make tf4mono&lt;br /&gt;more useful to you. Better support for locking files? Handling merge conflicts?&lt;br /&gt;Easier building on win32 platforms? More GUI support? Let me know!&lt;br /&gt;&lt;br /&gt;The master/trunk branch of tf4mono just got a "stats" command which makes use of&lt;br /&gt;&lt;a href="http://blogs.msdn.com/buckh/archive/2005/06/29/434099.aspx"&gt;/VersionControl/v1.0/administration.asmx&lt;/a&gt; to generate some server statistics.&lt;br /&gt;&lt;br /&gt;Here's some sample output:&lt;pre&gt;(~/Source/tfs-lsg-1.0) tf stats&lt;br /&gt;Files:           812421&lt;br /&gt;Folders:         20033&lt;br /&gt;Groups:          481&lt;br /&gt;Pending Changes: 7907&lt;br /&gt;Shelvesets:      180&lt;br /&gt;Users:           184                                                                         &lt;br /&gt;Workspaces:      154&lt;br /&gt;&lt;/pre&gt;I plan on augmenting this output a bit, but its a good start.&lt;br /&gt;&lt;br /&gt;The most nagging issue for me is actually a NTLM bug in mono that I keep hoping someone will eventually fix. It could be bug&lt;br /&gt;&lt;a href="http://bugzilla.ximian.com/show_bug.cgi?id=80687"&gt;#80687&lt;/a&gt;, though I'm not sure of it. On windows boxen, tf4mono never gives occasional auth failures, but on mono it does - especially on a fast network.&lt;br /&gt;&lt;br /&gt;By accident, I noticed that I never saw any auth failures when working from home&lt;br /&gt;over a VPN, but at work I'd see the auth failures quite regularly. If I route my&lt;br /&gt;TFS traffic at work thru my home machine, thru the VPN, and back to work I never see auth failures. So it seems the faster the network the more likely you are to see this NTLM bug in mono.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7725362939708178429?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7725362939708178429/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7725362939708178429' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7725362939708178429'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7725362939708178429'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/09/monthly-sleep-deprivation.html' title='Monthly Sleep Deprivation'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-342225008038426842</id><published>2007-08-30T16:03:00.000-04:00</published><updated>2007-08-30T16:15:36.658-04:00</updated><title type='text'>tf4mono for windows</title><content type='html'>Thanks to &lt;a href="http://nsis.sourceforge.net/"&gt;Nullsoft Scriptable Install System&lt;/a&gt;, I've created some win32 installation packages for &lt;a href="http://code.google.com/p/tf4mono/"&gt;tf4mono&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;There are two install options.&lt;br /&gt;&lt;br /&gt;The first, &lt;a href="http://tf4mono.googlecode.com/files/tf4mono-base-0.5.1-rc1.exe"&gt;tf4mono-base-0.5.1-rc1.exe&lt;/a&gt;,  has been compiled without any GUI code. It has no external dependencies and should run on any win32 box with the .Net 2.0 framework installed.&lt;br /&gt;&lt;br /&gt;The second package, &lt;a href="http://tf4mono.googlecode.com/files/tf4mono-full-0.5.1-rc1.exe"&gt;tf4mono-full-0.5.1-rc1.exe&lt;/a&gt;,  includes the &lt;a href="http://flickr.com/photos/11527903@N04/sets/72157601529234621/detail/"&gt;graphical TF explore command&lt;/a&gt;. To run this version on win32, you must first install the &lt;a href="http://forge.novell.com/modules/xfmod/project/?gtks-inst4win"&gt;Gtk# Installer for Windows&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Neither package adds the tf4mono installation folder to the SYSTEM or USER path. You'll have to do this by hand for now.&lt;br /&gt;&lt;br /&gt;If you're on a windows box and have a few minutes to test out the package, please do so. Any feedback on how they work would be awesome.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-342225008038426842?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/342225008038426842/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=342225008038426842' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/342225008038426842'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/342225008038426842'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/08/tf4mono-for-windows.html' title='tf4mono for windows'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7747648020765629702</id><published>2007-08-15T22:31:00.001-04:00</published><updated>2007-08-15T22:37:56.410-04:00</updated><title type='text'>Explore Command</title><content type='html'>Rather than try to learn how to write a monodevelop plugin and the Gtk# API all at the same time, I thought I'd start out by just getting my head around Gtk#.&lt;br /&gt;&lt;br /&gt;Consequently, I've been working on a &lt;b&gt;tf explore&lt;/b&gt; command that popups a Gtk# Application that lets you explore the TFS repository you're connected to. Here's what it looks like thus far:&lt;br /&gt;&lt;br /&gt;&lt;div style="float: left; margin-right: 10px; margin-bottom: 4px;"&gt; &lt;a href="http://www.flickr.com/photos/11527903@N04/1132171883/" title="photo sharing"&gt;&lt;img src="http://farm2.static.flickr.com/1279/1132171883_84c5ecee08_m.jpg" alt="" style="border: 2px solid rgb(0, 0, 0);" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;I've been pretty impressed with the available widgets and their ease of use.&lt;br /&gt;&lt;br /&gt;I can't quite figure out yet how to show a WATCH cursor when the TF query is lengthy - I've tried several different incantations but all to no avail.&lt;br /&gt;&lt;br /&gt;That's the only thing that seems like it should be easier.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7747648020765629702?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7747648020765629702/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7747648020765629702' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7747648020765629702'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7747648020765629702'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/08/explore-command.html' title='Explore Command'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm2.static.flickr.com/1279/1132171883_84c5ecee08_t.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-1531798419401718677</id><published>2007-07-31T12:02:00.000-04:00</published><updated>2007-07-31T12:43:11.342-04:00</updated><title type='text'>Read-Write Local Trees in TFS</title><content type='html'>One rather annoying feature of &lt;a href="http://msdn2.microsoft.com/en-us/teamsystem/aa718934.aspx"&gt;TFS&lt;/a&gt; is that all files are marked read-only in your local tree until you checkout a file, at which point it becomes read-write. Its not hard to find &lt;a href="http://www.teamfrustrationserver.com/blog/2007/07/11/read-only-rage/"&gt;other&lt;/a&gt; &lt;a href="http://www.codeplex.com/TFSCCNetPlugin/WorkItem/View.aspx?WorkItemId=8086"&gt;people&lt;/a&gt; who also find this very annoying.&lt;br /&gt;&lt;br /&gt;I was hopeful MS would make read-write local trees an option in the &lt;a href="http://en.wikipedia.org/wiki/Microsoft_Visual_Studio#Visual_Studio_2008"&gt;Orcas release of TFS&lt;/a&gt;, but recently came across this &lt;a href="http://blogs.msdn.com/buckh/archive/2007/07/13/get-date-and-time-read-write-vs-read-only.aspx"&gt; post by Buck Hodges&lt;/a&gt; where he explains it won't be a feature in Orcas.&lt;br /&gt;&lt;br /&gt;I was annoyed enough to add support for read-write files in version 0.5.0 of &lt;a href="http://code.google.com/p/tf4mono/"&gt;tf4mono&lt;/a&gt;.&lt;br /&gt;To enable it just type&lt;br /&gt;&lt;pre&gt;tf config File.ReadWrite true&lt;/pre&gt;Then from that point on &lt;b&gt;tf get, tf undo, tf checkin, tf add&lt;/b&gt; and friends will do the right thing.&lt;br /&gt;&lt;br /&gt;If you know what you're doing you can compile and run the tf4mono client on Windows, either against the MS TFS assemblies or with the tf4mono implementation of these assemblies.&lt;br /&gt;&lt;br /&gt;I didn't want to lose this flexibility but to really do this right API level support would be required. Subsequently, I ended up teaching the tf4mono TFS assemblies to read the TF client XML config file. This is slightly distasteful but the standard assemblies already read the workspace cache XML file so I can live with it. I really didn't want to bolt this read-write stuff on top of the existing code, because that would have meant alot more stat's and chmod's under the hood.&lt;br /&gt;&lt;br /&gt;Anyway to really make this work I also had to beef up the &lt;b&gt;tf online&lt;/b&gt; command, but that will have to wait for another blog post.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-1531798419401718677?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/1531798419401718677/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=1531798419401718677' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1531798419401718677'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1531798419401718677'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/07/read-write-local-trees-in-tfs.html' title='Read-Write Local Trees in TFS'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-3584210959072257129</id><published>2007-07-16T21:44:00.000-04:00</published><updated>2007-07-16T22:18:47.762-04:00</updated><title type='text'>Mono.GetOptions working overtime</title><content type='html'>When I started &lt;a href="http://code.google.com/p/tf4mono/"&gt;tf4mono&lt;/a&gt;,  the command line Team Foundation client, I didn't really want to invent yet another option parsing library and it seemed like &lt;a href="http://www.go-mono.com/docs/monodoc.ashx?link=N%3aMono.GetOptions"&gt;Mono.GetOptions&lt;/a&gt;  would fit the bill.&lt;br /&gt;&lt;br /&gt;Over time though I found myself adding more and more options to my Options.cs file, which really got out of hand. Finally, I found myself in a hotel room in &lt;a href="http://www.google.com/maps?q=Tulsa,+OK,+USA&amp;sa=X&amp;amp;oi=map&amp;ct=title"&gt;Tulsa, Oklahoma&lt;/a&gt; with no internet access - a perfect time for code cleanups!&lt;br /&gt;&lt;br /&gt;I ended up keeping global options in my Options.cs and moving all command specific options into each *Command.cs file, then I give both the global driver and the desired command a crack at parsing the arg array. With a little extra hackery to support &lt;a href="http://code.google.com/p/tf4mono/wiki/ClientDifferences"&gt;command chaining&lt;/a&gt;, I ended up being able to do a huge code cleanup and really enhance the builtin &lt;b&gt;tf help&lt;/b&gt; command.&lt;br /&gt;&lt;br /&gt;While I was mucking around in there, I added a custom "Command" attribute to decorate my &lt;a href="http://en.wikipedia.org/wiki/Command_pattern"&gt;Command classes&lt;/a&gt;. Now each command in the tf client looks something like this:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;[Command("history", "Display changelog history for specified file.")]&lt;br /&gt;class HistoryCommand : Command&lt;br /&gt;{&lt;br /&gt;[Option("Recursive", "R", "recursive")]&lt;br /&gt;private bool OptionRecursive = false;&lt;br /&gt;&lt;br /&gt;[Option("Stop After", "", "stopafter")]&lt;br /&gt;private int OptionStopAfter = -1;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;With a little &lt;a href="http://msdn2.microsoft.com/en-us/library/system.reflection.aspx"&gt;reflection&lt;/a&gt;, the tf client now has a reasonable &lt;b&gt;tf help &amp;lt;cmd&amp;gt;&lt;/b&gt; feature, which gives you:&lt;pre&gt;(/usr/local/src/tfs/tools/tf) tf help hist&lt;br /&gt;history (alias hist)&lt;br /&gt;Display changelog history for specified file.&lt;br /&gt;&lt;br /&gt;Valid options:&lt;br /&gt;/format:ARG        Format "brief" or "detailed" (also /F:ARG)&lt;br /&gt;/recursive         Recursive (also /R)&lt;br /&gt;/stopafter:ARG     Stop After&lt;br /&gt;/workspace:ARG     Workspace name (also /W:ARG)&lt;br /&gt;&lt;/pre&gt;Now I just need to figure out a way to generate the &lt;a href="http://www.methods.co.nz/asciidoc/"&gt;asciidoc&lt;/a&gt; manpage source file from the builtin help and life will be golden.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-3584210959072257129?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/3584210959072257129/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=3584210959072257129' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3584210959072257129'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3584210959072257129'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/07/monogetoptions-working-overtime.html' title='Mono.GetOptions working overtime'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-1649094590663322977</id><published>2007-06-26T07:00:00.000-04:00</published><updated>2007-06-26T07:12:10.458-04:00</updated><title type='text'>Working on the fringes</title><content type='html'>Probably like most programmers, I enjoy coding a lot more than I do writing documentation. To combat this, I've decided I should force myself to write one new doc for every release of &lt;a href="http://code.google.com/p/tf4mono/wiki/ClientDifferences"&gt;tf4mono&lt;/a&gt; that I make.&lt;br /&gt;&lt;br /&gt;To that end, for the upcoming 0.4.7 release, I've whipped up a summary of the &lt;a href="http://code.google.com/p/tf4mono/wiki/ClientDifferences"&gt;differences between the tf4mono TF client, and the standard MS client.&lt;/a&gt; In particular, where is tf4mono's TF better, and where is it worse. Lately, I've personally found myself leveraging the tf4mono client on my windows boxen to great benefit. YMMV.&lt;br /&gt;&lt;br /&gt;As an aside, if you are looking for help with tf4mono, consider the new &lt;a href="http://groups.google.com/group/tf4mono"&gt;Google Group&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-1649094590663322977?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/1649094590663322977/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=1649094590663322977' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1649094590663322977'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1649094590663322977'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/06/working-on-fringes.html' title='Working on the fringes'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-7237388420057309007</id><published>2007-06-21T08:03:00.000-04:00</published><updated>2007-06-21T08:38:45.962-04:00</updated><title type='text'>tf4mono on windows and ntlm authentication</title><content type='html'>Recently, I've had occasion to run not just the &lt;a href="http://code.google.com/p/tf4mono/"&gt;tf4mono&lt;/a&gt; "&lt;a href="http://tf4mono.googlecode.com/svn/trunk/tools/tf/"&gt;tf client&lt;/a&gt;", but also the tf4mono &lt;a href="http://tf4mono.googlecode.com/svn/trunk/class/"&gt;TeamFoundation assemblies&lt;/a&gt; on a windows virtual machine.&lt;br /&gt;&lt;br /&gt;This uncovered several platform related issues, like code that was &lt;a href="http://repo.or.cz/w/tfs.git?a=commitdiff;h=1f3f82caf0c1d34913a4b313b7e489069bfbd139"&gt;deleting&lt;/a&gt; or &lt;a href="http://repo.or.cz/w/tfs.git?a=commitdiff;h=e8cf5db4c5281ae089242e8133f6aaf4aa65b125"&gt;updating&lt;/a&gt; files that were read-only and deleting files that we &lt;a href="http://repo.or.cz/w/tfs.git?a=commitdiff;h=1114840a09a2a819f6f82b46331acab9ccd192fc"&gt;still held open&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;More importantly though, I got a new perspective on the longstanding issues I've seen with NTLM authentication on Mono from my linux box. Tf4mono basically is a client for a bunch of Team Foundation web services that frequently are setup to use NTLM for authentication. So here is what I found:&lt;ul&gt;&lt;br /&gt;&lt;li&gt;on mono/linux I see periodic, somewhat predictable NTML authentication failures&lt;/li&gt;&lt;br /&gt;&lt;li&gt;on mono/windows, it fails every time&lt;/li&gt;&lt;br /&gt;&lt;li&gt;on msclr/windows, &lt;b&gt;it works&lt;/b&gt; every time&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;I'm of course somewhat encouraged by the msclr/windows findings, which lead me to believe its not a problem in my code. Whew!&lt;br /&gt;&lt;br /&gt;When I dug into the issue previously on mono/nix, I saw something somewhat similar to &lt;a href="http://bugzilla.ximian.com/show_bug.cgi?id=80687"&gt;bugzilla bug 80687&lt;/a&gt;. Jim Matysczak suggests the problem is "&lt;i&gt;that the the type 1 message and the type 3 message are not being sent in the same socket connection which is required by the protocol&lt;/i&gt;."&lt;br /&gt;&lt;br /&gt;By the way, all the windows related changes are all in the project subversion and git repositories, but not in any release yet.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-7237388420057309007?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/7237388420057309007/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=7237388420057309007' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7237388420057309007'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/7237388420057309007'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/06/tf4mono-on-windows-and-ntlm.html' title='tf4mono on windows and ntlm authentication'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-3510690052651033297</id><published>2007-06-09T20:53:00.000-04:00</published><updated>2007-06-09T22:38:08.284-04:00</updated><title type='text'>TechEd Musings</title><content type='html'>I spent the last week at TechEd Orlando and blogged about much of it on the internal company blog site. I thought I'd take a minute to post some thoughts for the Mono community as well.&lt;br /&gt;&lt;br /&gt;It seems like "platform services" like &lt;a href="http://office.microsoft.com/en-us/sharepointtechnology/FX100503841033.aspx"&gt;Windows Sharepoint Services&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Active_Directory"&gt;Active Directory&lt;/a&gt; present a unique challenge  when we're talking about Mono enabling cross platform solutions. They seem challenging to me because they're not just about writing more library code, you've got copyrighted UI images, underlying database engines, directory services, etc..&lt;br /&gt;&lt;br /&gt;At TechEd, I definitely got the impression that developing ASP.NET solutions on top of Windows Sharepoint Services would become increasingly common and of course, be encouraged by MS. You get the seem impression about &lt;a href="http://technet2.microsoft.com/WindowsServer/en/library/050392bc-c8f5-48b3-b30e-bf310399ff5d1033.mspx"&gt;ADFS&lt;/a&gt;, which enables federated claims-based applications.&lt;br /&gt;&lt;br /&gt;Does this mean more and more ASP.Net applications will become unportable, or will the community rise to the challenge of building out these type of server "platform services"? I'm wondering.&lt;br /&gt;&lt;br /&gt;On another note, it was neat to see how MS is building on the .Net 3.5 System.ServiceModel.Web stuff and the &lt;a href="http://blogs.msdn.com/data/archive/2007/04/28/microsoft-s-data-access-strategy.aspx"&gt;ADO.Net Entity Framework&lt;/a&gt;, to build out &lt;a href="http://astoria.mslivelabs.com/"&gt;Project Astoria&lt;/a&gt;, which is a REST based framework for data access for Ajax/Silverlight type clients. I'm sure hoping that the WCF mono bits around System.ServiceModel.Web that enable this  don't prove hard to implement.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-3510690052651033297?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/3510690052651033297/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=3510690052651033297' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3510690052651033297'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3510690052651033297'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/06/teched-musings.html' title='TechEd Musings'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-1709906523090459882</id><published>2007-05-24T15:50:00.000-04:00</published><updated>2007-05-24T15:51:59.065-04:00</updated><title type='text'>Demystifying a Black Art</title><content type='html'>I'm working my way through Steve McConnell's &lt;i&gt;Software Estimation: Demystifying the Black Art&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;He starts off by making it clear that estimates are NOT the same thing as targets or commitments. Often business partners might ask you for an "estimate" when really what they want is a commitment to a target date.&lt;br /&gt;&lt;br /&gt;In fact, estimates should be seen as &lt;b&gt;probability statements&lt;/b&gt;. Steve recommends you always give estimates as a range like "2 to 5 weeks", or as a number plus a probability like "I'm about 80% confident I can get this done in 4 weeks." I personally think giving a range is a much better practice - business partners are too quick to forget the stated probability in the latter approach.&lt;br /&gt;&lt;br /&gt;So what is a good estimate, for most people not building bridges or nuclear reactors? Here's how he defines a "good estimate:"&lt;blockquote&gt;A good estimate is an estimate that provides a clear enough view of the project reality to allow the project leadership to make good decisions about how to control the project to hit its targets.&lt;/blockquote&gt;The idea? Get the target date and estimate date range &lt;b&gt;close enough&lt;/b&gt; and a good project manager can get the project in on time be shifting resources, simplifying/dropping requirements, etc. The estimate serves as a way to see if you are close enough to make the target date a reality with tweaks along the way.&lt;br /&gt;&lt;br /&gt;Anyway, if you are interested in learning more, consider &lt;a href="http://www.stevemcconnell.com/est.htm"&gt;buying the book.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-1709906523090459882?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/1709906523090459882/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=1709906523090459882' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1709906523090459882'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/1709906523090459882'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/05/demystifying-black-art.html' title='Demystifying a Black Art'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-8545284800860380493</id><published>2007-05-23T08:44:00.000-04:00</published><updated>2007-05-23T09:23:11.487-04:00</updated><title type='text'>System.ServiceModel.Syndication</title><content type='html'>Eventually, I'd like to add a "tfsweb" capability to &lt;a href="http://code.google.com/p/tf4mono/"&gt;Team Foundation for Mono&lt;/a&gt;, that would, as much as possible, mimic &lt;a href="http://www.kernel.org/git/gitweb.cgi"&gt;gitweb's&lt;/a&gt; capabilities.&lt;br /&gt;&lt;br /&gt;With this in mind, I'll need to create Atom/RSS feeds to which users can subscribe so they can track changes to a TFS repository over time.&lt;br /&gt;&lt;br /&gt;In hopes of not having to rewrite the Atom/RSS code sometime in the future to leverage the .Net 3.5 syndication classes, I'm taking a crack at adding the System.ServiceModel.Syndication clases to the mono olive tree.&lt;br /&gt;&lt;br /&gt;As a starting point, I'm trying to make Matt W's &lt;a href="http://blogs.msdn.com/mwinkle/archive/2007/02/28/wcf-and-wf-in-quot-orcas-quot.aspx"&gt;syndication  example code&lt;/a&gt; produce the same output  on MS.Net and Mono. Is anyone else working on this namespace? Right now I have:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: courier new;"&gt;Assembly/AssemblyInfo.cs            |   57 ++++++++++++++++++++++++++++++++&lt;br /&gt;Atom10Serializer.cs                 |   54 ++++++++++++++++++++++++++++++&lt;br /&gt;Makefile                            |   22 ++++++++++++&lt;br /&gt;README                              |    4 ++&lt;br /&gt;SyndicationContent.cs               |   45 +++++++++++++++++++++++++&lt;br /&gt;SyndicationFeed.cs                  |   63 +++++++++++++++++++++++++++++++++++&lt;br /&gt;SyndicationItem.cs                  |   57 ++++++++++++++++++++++++++++++++&lt;br /&gt;SyndicationSerializer.cs            |   50 ++++++++++++++++++++++++++++&lt;br /&gt;System.ServiceModel.Web.dll.sources |   11 ++++++&lt;br /&gt;TextSyndicationContent.cs           |   64 ++++++++++++++++++++++++++++++++++++&lt;br /&gt;TextSyndicationContentKind.cs       |   40 ++++++++++++++++++++++&lt;br /&gt;11 files changed, 467 insertions(+)&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Not a whole lot yet, but on the other hand this looks like a pretty straightforward namespace to implement that won't take alot of time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-8545284800860380493?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/8545284800860380493/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=8545284800860380493' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8545284800860380493'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8545284800860380493'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/05/systemservicemodelsyndication.html' title='System.ServiceModel.Syndication'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-3546592427776987476</id><published>2007-05-20T16:16:00.001-04:00</published><updated>2007-05-20T16:25:10.814-04:00</updated><title type='text'>tf4mono version 0.4.4</title><content type='html'>I've pretty much implemented all the features I'm looking for in a Team Foundation client for Mono, added all the usability enhancements necessary for things like password caching in the gnome keyring, and added some configuration extras for those who don't like the default behavior of the TF SCM client. Its all packaged up into the 0.4.4 release of &lt;a href="http://code.google.com/p/tf4mono/"&gt;tf4mono&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I tried taking a crack at implementing a TF version control plugin for MonoDevelop, but the way TF works is so much different than SVN, that I'm having second thoughts about this approach. I'm thinking now that maybe I should base something off of the Mono.Addins infrastructure. TF has all this stuff about workspaces, mapped paths, read-only files till checked out, etc. that don't mesh well with the traditional SVN/CVS model. Its more akin to Visual Source Safe.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-3546592427776987476?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/3546592427776987476/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=3546592427776987476' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3546592427776987476'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3546592427776987476'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/05/tf4mono-version-044.html' title='tf4mono version 0.4.4'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-3799220262323999999</id><published>2007-04-27T00:22:00.000-04:00</published><updated>2007-04-27T00:23:52.061-04:00</updated><title type='text'>tf4mono version 0.4.2</title><content type='html'>I just released a new version of Team Foundation for Mono. &lt;br /&gt;&lt;br /&gt;Highlights of this release include:&lt;br /&gt;&lt;br /&gt;1. tf.exe supports two new commands: "ls-files" and "properties".&lt;br /&gt;&lt;br /&gt;ls-files has options to show modified, out-of-date, unknown, and locally deleted file listings. This functionality doesn't exist in the Microsoft tf.exe client, and may be a reason to run tf4mono's tf.exe on Windows.&lt;br /&gt;&lt;br /&gt;2. the tf.exe undo command now restores deleted files&lt;br /&gt;&lt;br /&gt;3. tf.exe now compiles/runs on MS CLR.&lt;br /&gt;&lt;br /&gt;4. Added API support for querying ExtendedItems.&lt;br /&gt;&lt;br /&gt;5. Debian packages via "make dist"&lt;br /&gt;&lt;br /&gt;6. Add GettingEventHandler, PendingChangeEventHandler, ProcessingChangeEventHandler, and OperationStatus delegates.&lt;br /&gt;&lt;br /&gt;7. Many bug fixes were made to APIs, and client utility while testing on Windows. Greatly improved handling of ~/.tf/VersionControl.config cache.&lt;br /&gt;&lt;br /&gt;8. Start of MonoDevelop plugin based on Subversion plugin. I'm not quite sure I can really map the way Team Foundation works onto the MonoDevelop VersionControl framework, but we'll see. Fundamentally, the way TFS works is much different than SVN.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-3799220262323999999?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/3799220262323999999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=3799220262323999999' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3799220262323999999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/3799220262323999999'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/04/tf4mono-version-042.html' title='tf4mono version 0.4.2'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2207149810921558692.post-8030843696451463369</id><published>2007-04-19T21:19:00.000-04:00</published><updated>2007-04-19T21:22:54.047-04:00</updated><title type='text'>tf4mono project site</title><content type='html'>I'm setting up a &lt;a href="http://code.google.com/p/tf4mono/"&gt;Google Code project site&lt;/a&gt; for my Team Foundation for Mono project.&lt;br /&gt;&lt;br /&gt;I've been hacking out the Version Control assemblies for some time and recently started working on a MonoDevelop plugin.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2207149810921558692-8030843696451463369?l=ropeonfire.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ropeonfire.blogspot.com/feeds/8030843696451463369/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2207149810921558692&amp;postID=8030843696451463369' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8030843696451463369'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2207149810921558692/posts/default/8030843696451463369'/><link rel='alternate' type='text/html' href='http://ropeonfire.blogspot.com/2007/04/tf4mono-project-site.html' title='tf4mono project site'/><author><name>jr</name><uri>http://www.blogger.com/profile/10240654457551049365</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_55fIZQMSGNU/TK9J4yePbPI/AAAAAAAAAHs/LBxDdL5Ykjs/S220/linkedin-pic.jpg'/></author><thr:total>3</thr:total></entry></feed>
