18 February 2010

I shall be released!

Had one of those brain-cramping moments at work the other day. As part of our project, we have to use a tool developed by another team at our company. We were using release "1.0 RC1". Normally, an "RC" (release candidate) release means that the official "1.0" release is imminent, and should be identical to the "RC" release. But the other day, we were informed that support for a new feature will be available in: release "1.0 RC2". What??!?

The theory behind a "release candidate" is that the code is done and ready for release (hence the name). All the bugs have either been fixed or documented, and you're just giving users a pre-release to see if any new bugs turn up in the field. The usual process is that if no major bugs are found, you'll just re-issue the release candidate as the final version. There can be trivial changes, like changing any embedded release IDs from "1.0 RC1" to "1.0" or maybe "1.0 FINAL", but absolutely NO code changes. Obviously, my company's suffering from a significant nomenclature failure. There's a term for what they're releasing, and that's a "milestone release". So why didn't they use that term? To quote the Tao (of Programming): "These are great mysteries."

I was fuming about this the other day, and I came up with a hierarchy of release types. Check these out and see if I've covered all the bases:































NameNumbering schemeDescription
Major releaseX[.0[.0]]Significant improvements through major rewrites. May not have an actual release number, but instead a distinct name.
Minor releaseX.M[.0]Introduces new features, but is mostly compatible with previous minor releases within its major release.
Maintenance releaseX.M.mUsually reserved for bug-fixes, may include minor functional improvements (like support for new devices or file formats).
Release candidateX.M.m RC-nSort of a "shakedown cruise". Note that there may be several release candidates, depending on how vigorous your beta-testers are.
Preview releaseX.M.m [Feature] PreviewAn interim release to permit testers to play with some new feaure(s). Preview releases are intended to be used outside of the development team, so they should be relatively free of bugs, and any limitations should be clearly documented.
Milestone releaseX.M.m M-nA periodic release, usually when some major new functionality has been introduced. Very similar to a feature release, the only difference is that feature releases are usually targeted at end users, and milestone releases are more oriented towards development partners or other allied groups. Milestones may be more buggy, or have the bare minimum of functionality required to "support" a feature.
Interim releaseVariesA periodic release, usually just to the QA team. A work-in-progress, interim releases serve to aggregate bug fixes and incremental improvements. Some companies may require that nothing new be broken, but often there are no guarantees beyond the development team's assurance that the release represents their best effort to date.
Daily buildX.M.m-YYYYMMDDUsually only seen at sites employing Continuous Integration (CI), this is a build created from a snapshot of the development repository, often kicked off at midnight. May be used by QA teams, but often only monitored for regression test results. Numbering scheme varies, often a project code name is used instead of a release number.
Latest buildVaries, possibly [Project name]-YYYYMMDD_HH:MM:SSAgain, usually only seen at sites employing CI. This is a snapshot of the current source repository. Absolutely NO guarantees about quality or function beyond the fact that it compiles (which can be a significant milestone in itself, sometimes!)


So what sort of release hierarchy do you (or your team) use?

26 January 2009

Windows PowerShell: FAIL

What I wanted:
find . -name *.class -exec rm {} \;

What I was told would work:
Remove-Item .\* -recurse -include .class

What I would have settled for (if it had worked):

set fso = CreateObject("Scripting.FileSystemObject")
set fileExpr = new RegExp
fileExpr.Pattern = "class$"
fileExpr.IgnoreCase = True
set curDir = fso.GetFolder(".")
CleanDir curDir, fileExpr

Sub CleanDir(Dir, Expr)
for each File in Dir.Files
if Expr.test(File.Name) then
fso.DeleteFile(Dir.Name & File.Name)
end if
next
for each SubDir in Dir.SubFolders
CleanDir SubDir, Expr
next
end sub


What I finally wound up doing:
[In windows explorer]
Right-click on directory->Search
Click in "Enter all or part of the file name" text box, type ".class"
Click "Search"
Wait
Hit Ctrl-A to select all
Hit "Delete"
Wait (for almost a full minute! WTF?)
Click close button.

Apparently they're shooting for a consistent level of difficulty no matter what the task. Replicate an enterprise database? Five minutes. Import it into Excel, flag outliers and print a formatted report? Five minutes. Delete the file after printing? Five minutes...

11 January 2008

Logitech SetPoint == teh suxx0r!

I like Logitech's trackballs. People were amazed that I would actually spend my own money for a Logitech trackball for my work computer when I had a perfectly good mouse. At last count I had two of the original Trackman Marble, two corded and one cordless Trackman Marble Wheel, and a Marble Mouse. I got the Marble Mouse because my thumb started to be tired if I worked on the computer too long at night (after working on one all day at the office). So, I have the Marble Mouse at work.

The Marble Mouse has two large buttons on either side, and it also has two small buttons just above the large ones. Logitech's SetPoint utility can be used to map those to a keypress or action. When I originally got the MM, I set it up so that the left small button paged down, and the right one paged up. I used it this way for so long, it's become second nature. So when they aren't mapped properly, it drives me nuts.

I just started a new job, with a new computer. I unpacked the MM and hooked it up and downloaded the latest SetPoint from the Logitech site. Launched it up and went to customize the buttons and WTF? I could map the left one to page up, but there was no page down listed. I checked out the installation directory, and there was this XML file (default.xml) with all these "handler sets" configured. It certainly looked like both page down and page up were supported, so why weren't they available? Further digging showed a ton of configuration files in the All Users/Application Data/Logitect/SetPoint directory, but they all had helpful names like "1000001F". So I STFW to see if anyone else had posted a solution to this. Someone had written an "uberButton" utility that claimed to enable all features for all buttons, but it was an 8M executable, which I thought was a little much for my purposes. So I poked around some more and found a directory named "Logitech/SetPoint" in my profile's application data directory. I checked, and lo and behold, there were the current mappings in a file named user.xml. They were references to the handler sets I had noticed in the default.xml file. So I whipped out Vim and changed the references to point to the page up and down handlers. There were also some application-specific "overrides" for these functions, so I set those up too. Logged out and back in and voila! The MouseMan rides (properly) again!

Here's what the configuration file looks like:


<Source>
<UserOptions>
<UserOption Name="BeepKeyboardSettings">0</UserOption>
<UserOption Name="ShowKeyboardPopupSettings">1</UserOption>
<UserOption Name="ShowKeyboardTraySettings">1</UserOption>
<UserOption Name="ShowTrayIcon">1</UserOption>
<UserOption Name="ShowKeyboardSettings">1</UserOption>
</UserOptions>
<Apps>
<App>c:\\progra~1\\micros~2\\office11\\outlook.exe</App>
<App>c:\\program files\\outlook express\\msimn.exe</App>
<App>c:\\progra~1\\micros~2\\office11\\winword.exe</App>
<App>c:\\progra~1\\micros~2\\office11\\powerpnt.exe</App>
<App>c:\\progra~1\\micros~2\\office11\\excel.exe</App>
</Apps>
<Devices>
<Device DisplayName="Marble Mouse" Class="PointingDevice" Model="16777328" NumberOfButtons="4" ConnectionID="">
<Param ACCELERATION="1" AIRACCELERATION="4" AirSpeedX="50" AirSpeedY="50"
AppSpecific="0" ButtonSwapped="0"
DefaultGameModeResolutionPresets=""
DefaultGameModeResolutionPresetsY="" DistinctAirCursor="1"
EnableCentering="1" GameDeviceSuggestGameMode="0"
GameDeviceSuggestOS="0" GameModeButtonAddGame="0"
GameModeButtonResDown="0" GameModeButtonResUp="0"
GameModeButtonResUpWrap="0" GameModeKeepAcceleration="1"
GameModeKeepKeypress="1" GameModeKeepOther="0"
GameModeKeepRightLeft="1" GameModeKeepSpeed="1"
GameModeNotification="0" GameModeResolutionNbPreset="0"
GameModeResolutionPresets="" GameModeResolutionPresetsY=""
GameParticipate="0" GameParticipateDefault="0" GamePowerMode="0"
GameReportEnabled="1" GameReportRate="2" GameResIndex="2"
GameUseAdvanced="0" GamingType="0" LowBatPercentLevel="5"
MenuLocBottom="-1" MenuLocLeft="-1" MenuLocRight="-1"
MenuLocTop="-1" MenuSize="0" OfficePowerMode="0"
ShowButtonsPage="1" SmartMove="0" SpeedX="25" SpeedY="25"
StromboliMice="0" TouchPadMice="0" TrackballMice="1" Trails="0"
TrailsLength="0" UserBatteryMode="0" VScrollAcceleration="0"
VScrollSpeed="0" VoIPClient="" smartshiftmode="0"/>
<Buttons>
<Button Number="1" Name="1">
<Param IconLoc="" Type=""/>
<Trigger Class="ButtonPress">
<Param Button="1" FirstRepeatDelay="0" RepeatDelay="0"
Silent="1" Type="0"/>
<TriggerState Name="ButtonDownUp" HandlerSet="LeftClick">
<Handler Class="MouseButton">
<Param ButtonName="PrimaryButton"/>
</Handler>
</TriggerState>
</Trigger>
</Button>
<Button Number="2" Name="2">
<Param IconLoc="" Type=""/>
<Trigger Class="ButtonPress">
<Param Button="2" FirstRepeatDelay="0" RepeatDelay="0"
Silent="1" Type="0"/>
<TriggerState Name="ButtonDownUp" HandlerSet="RightClick">
<Handler Class="MouseButton">
<Param ButtonName="SecondaryButton"/>
</Handler>
</TriggerState>
</Trigger>
</Button>
<Button Number="4" Name="3">
<Param IconLoc="" Type=""/>
<Trigger Class="ButtonPress">
<Param Button="4" FirstRepeatDelay="0" RepeatDelay="0"
Silent="0" Type="0"/>
<TriggerState Name="ButtonDownUp" HandlerSet="PageDown">
<Handler Class="KeyStroke">
<Param KeyName="{PGDN}"/>
</Handler>
<AppOverride App="mm.exe" HandlerSet="AppOverride_MindMapperBackward"/>
<AppOverride App="winword.exe" HandlerSet="AppOverride_WordBackward"/>
<AppOverride App="msimn.exe" HandlerSet="AppOverride_OutlookExpressBackward"/>
<AppOverride App="outlook.exe" HandlerSet="AppOverride_OutlookBackward"/>
<AppOverride App="powerpnt.exe" HandlerSet="AppOverride_PowerPointBackward"/>
<AppOverride App="nlnotes.exe" HandlerSet="AppOverride_LotusNotesBackward"/>
<AppOverride App="excel.exe" HandlerSet="AppOverride_ExcelBackward"/>
</TriggerState>
</Trigger>

</Button>
<Button Number="5" Name="4">
<Param IconLoc="" Type=""/>
<Trigger Class="ButtonPress">
<Param Button="5" FirstRepeatDelay="0" RepeatDelay="0"
Silent="0" Type="0"/>
<TriggerState Name="ButtonDownUp" HandlerSet="PageUp">
<Handler Class="KeyStroke">
<Param KeyName="{PGUP}"/>
</Handler>
<AppOverride App="mm.exe" HandlerSet="AppOverride_MindMapperForward"/>
<AppOverride App="winword.exe" HandlerSet="AppOverride_WordForward"/>
<AppOverride App="msimn.exe" HandlerSet="AppOverride_OutlookExpressForward"/>
<AppOverride App="outlook.exe" HandlerSet="AppOverride_OutlookForward"/>
<AppOverride App="powerpnt.exe" HandlerSet="AppOverride_PowerPointForward"/>
<AppOverride App="nlnotes.exe" HandlerSet="AppOverride_LotusNotesForward"/>
<AppOverride App="excel.exe" HandlerSet="AppOverride_ExcelForward"/>
</TriggerState>

</Trigger>
</Button>
</Buttons>
</Device>
</Devices>
</Source>


You can browse through the default.xml file and check out what kind of functions are available. This file appears to cover all Logitech keyboards and mice, so there are a lot of things available (zoom in/out, volume up/down, standby/restart/shutdown/logoff, VoIP w/Skype, browser next/previous/search...). From the looks of it, you should also be able to create your own handler sets to handle app-specific functions, or even launch external programs. So if you have to perform some obnoxious series of commands on a regular basis (like, say, FTP an HTML file to a server and launch a browser to look at it), you could script those and bind them to a mouse button! Could be really handy, given a suitably bizarre set of circumstances...

15 October 2007

Selling out -- Round II

For my first "real" job, I was hired out of college before I'd finished my degree. I left to move to The Big City (Chicago) and to make triple what I'd been earning with two half-time computer programming jobs in Iowa. It was rough, as move-to-the-big-city-from-Hicksville-and-get-a-real-job first jobs often are, but the company I worked for was good, I really liked the people I worked with, and I was both challenged to come up with innovative solutions and given the leeway (if not the budget) to implement whatever solution I deemed best. However, the company was a non-profit, and I got discouraged whenever I talked with any of my old college friends who had likewise gotten real jobs and were making significantly more than I was. So, after a couple of years I jumped ship, selling out for a 20% raise and a change of scenery (to Philadelphia). The people at my new job were nice enough, but I was now a junior developer, and had to follow corporate standards and have a lot of my projects designed by some one else. I missed my old job, but there was no way to go back.

Fast forward to today. I'm working for a company I like, with people I really get along with, and I have the freedom to implement whatever solutions I like using whatever technologies I like. It's taken me a good four years of consulting and networking to get where I'm at, and I'm finally happy about my job for the first time in a long time. So I probably should have expected the phone call.

As it turns out, somebody (a recruiter) I'd worked with when I was consulting had kept a copy of my resume. He'd never been able to place me because I was only looking for jobs in Lincoln, and there weren't many companies in Lincoln that used Java. Now, it seems, one of the few that did was looking for new senior-level staff. So, under the misapprehension that "it never hurts to talk", I sent an updated copy of my resume to the recruiter. "Just to see what they had to say". Well, what they said was basically "20+% pay raise, team technical lead, possible promotion to junior architecture position within a year". So, I did what anyone who had been cast aside by his corporate overlord at some point in his career would do. I jumped. Sold out. Turned my back on the folks who had given me the freedom to exercise my technical judgement and the support to make me successful at it, and headed on down the line.

[Actually, I'm still on good terms with them, and agreed before I left to make myself available to provide whatever knowledge transfer I could once they hired my replacement, but still...]

So now, here I am, with my big paycheck and my serious-sounding title (not quite "Lord of the Realm, Defender of the Faith, Emperor of the Enterprise Service Bus", but better than "Spec Programmer II") and I'm wondering how I'm going to fit in around here. Everybody's very nice, and I've been able to contribute and I have projects and everything, but I still don't feel like I did the right thing. I mean, I did (it wasn't just for the money, there were significant career-direction factors that played a part), it just doesn't feel that way yet.

13 June 2007

The JAXP hits!!--More--

I had a spare moment, so I thought I'd shoot myself in the foot. Well, the shooting was unintentional, but it happens so often under these circumstances, you'd think I'd have figured out when to flinch by now.

Standard near-end-of-project clean-up, replacing any remaining "err.printStackTrace()" calls with proper logging statements and other tidying-up. I had in my notes "Add input XML validation". We hadn't had any problems with XML formatting before, but it seemed like a good thing to have when we went into production (can you hear the warning siren going off?). So I cons up a quick XMLValidator class with an inputIsValid() method and get that working. Plug it into the project and test all the various inputs we have, everything is happy, so I deploy it to our test server and have our external development partner have a go at it.

On the first run, he reports "Hey, it gives me a validation error." So we check, and sure enough, he's got one of the attributes misspelled. We fix that and re-submit and he gets another one. We check, and find another one. Fix and re-submit, get another validation failure. Only this time, we can't figure out why. The error message he's getting is "cvc-elt.1: Cannot find the definition of 'SchoolInfo'" (which is one of the elements in our schema). We can't find the problem, so I cut the XML from the server log and submit it on my test system where I can step through the validation with the debugger. Lo and behold, no problem! :/

So what's different between dev and test? Same app server, same jar file, whoops! I'm running Java 1.5 on my dev server, but the test server is 1.6 (why?) . Poke around a bit (OK, a lot) and find out that J2SE1.5 uses JAXP 1.3, while 1.6 uses JAXP1.4. Hmmm, so what's the difference? Well, it's not exactly documented anywhere, but when validating under JAXP1.4, you have to explicitly make the DocumentBuilderFactory (from which you create the parser that creates the document that you pass to the validator...) namespace-aware, and tell it that the resulting parser will be used for validation. Which makes sense, I guess, it's just that all this (and so much more!) is included with the JDK, and there's no real migration guide or set of comprehensive release notes that spells out these changes. And let's face it, if there were, it would be so big nobody would read it anyway. It would be nice if they could provide total backwards compatibility, but I know that's not always possible.

I dunno, I finally got Ruby 1.8.5 installed, and got Rails set up, and I'm working through Dave Thomas's Rails book, and I'm finding it's much quicker to develop in Rails than JEE. Of course, I haven't done anything much in Rails yet (and I don't know Ruby at all), but I've got a project that I had to shelve because it was taking too long to write in Java, and I'm going to dust off those notes and see if I can get it done in Ruby. If so, then I may have to roll out some Ruby utilities here at the orifice and see if I can get them entrenched. From what I can see, .NET isn't going to make web development any quicker than JEE, so I might as well push for something that is.

16 January 2007

Back from Web Services Hell

Well, what a long, strange, nasty, standards-ridden trip it's been! I started working on my WS project, using NetBeans and the Sun app server (aka the Glassfish Project). Everything's fine, I'm able to create a web service, deploy it on the server, create a WSDL file, create a test client, everything happy little web-service developers do. Obviously, things were going too well. So I get ready to make the service available to a third-party company we're working with. Since I can't just stick my development machine outside the firewall, I find out where we deploy our other web services. Well, it turns out all of our other web services are written in either .NET (our "approved" development platform) or LotusScript. Obviously, neither of these were going to work. However, we had a couple of legacy apps that were written in Java, so there was an app server available, it just wasn't accessible to outside users. I was assured that opening a hole in the firewall wouldn't be a problem, so I should just go ahead and test my service and once it was ready they'd open it up. OK, sounds good, I thought, and handed over a copy of my WAR file. "Oh, we can't use that." Um, what? "We need an EAR file." My blinking was met with a blank stare. OK, fine, whatever, it is but the work of a moment to create an EAR file that has my WAR file in it. I hand it over to the powers that be (who are really pretty good developers, but not sysadmins) and ask that they let me know when it's been deployed.

A couple of minutes later, I get a log file. A big one.

Seems our app server only supports Java up to version 1.4.2 (yeah, three guesses which one that is...), and I made heavy use of JAX-WS (which uses JSR-181 annotations, all of which are part of Java 1.5). Being the clever, resourceful person I am, I immediately went bitching to my manager. Turns out that even the latest version of The App Server That Time Forgot didn't support Java 1.5. However, as soon as I started looking, I discovered that they were releasing a new new version, which allegedly did support 1.5. So I downloaded it and immediately discovered that its support was pretty much limited to running 1.4.2 apps on a 1.5 JVM ("No JEE 5 support for you!").

So I got a copy of the popular open-source IDE that the app server company bases its tools on. Turns out the IDE doesn't have a "server profile" for the new new app server version, so it wasn't able to talk to it (even though it supported the new release, and the new new version was a minor release of that). So I got the "toolkit" that was supposed to work with the new new version. Turns out it didn't support JAX-WS. So I got the Web Services clip-on/extension/module. Turns out it was only mostly compatible with JAX-WS (full compatibility coming RSN). It further turns out that once the Web Services c/e/m was installed, the toolkit was only marginally compatible with the app server.

So I fudged and poked and tweaked and Googled and grepped and read and read and read. I loaded updates, I loaded patches, I got beta code, I got alpha code, I got third-party code. I got add-ons and clip-ons and extensions and modules. Finally, I caught myself humming "One Piece at at Time" by Johnny Cash, and that's when I decided it wasn't going to work.

Hmmmm, what to do, what to do... Do I scrap my current implementation and step back to those golden days of yesteryear and rewrite my code using JAX-RPC interface semantics? Or do I take that idle Oracle server I built and put the Sun app server on it and have them stick that out in the open? A quick conversation with my VP and it all started falling into place. It boiled down to the fact that I was proposing we use hardware we already owned, so we didn't have to coordinate with anyone else, and it was an already-proven solution. I was a month past my original "I could probably write this in a week or so" estimate, so expediency ruled the day and we did the deed. I was able to finish the work quickly once that was done (Total time creating web service with four methods: 2.5 wks. Total time trying to figure out how to make it work on app server X: 3 wks.).

I just deployed the service yesterday, and I'm waiting for the "We can't use this — it's just what we asked for!" call. Guess I'll just cement the curse by writing up the documentation...

19 October 2006

XSL FTW!

Just got a new project at work. We need to interface to another company's information system, and they want us to use web services. OK, cool, I'm all about the SOA (or will be once I do a little research), let's see what they've got. Interface specs, XML-RPC, all looks good until I get to the sample data they're sending. All their data is expressed as XML attributes. I know there's no hard consensus on elements vs. attributes, but I personally hold to the idea that attributes are for meta-data, and data itself should be in elements. I tried to ignore it, but it kept bugging me. I know that for the project I'm on, I'll just wind up working with what they give me, and suck it up. But I deperately wanted to convert it. Hey, it should be easy with XSL, right? Well, I suppose if you actually knew XSL, it probably would be. It took me a couple of hours to figure out the proper incantations of selectors and elements to get it done, but finally I did. And just on the off chance there's some other attribute-hater out there, here it is (sorry about the formatting!):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="*">
<xsl:variable name="tagName" select="name()"/>
<xsl:element name="{$tagName}">
<xsl:text>
</xsl:text>
<xsl:for-each select="@*">
<xsl:variable name="tagName" select="name()"/>
<xsl:element name="{$tagName}"><xsl:value-of select="."/></xsl:element>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>

<xsl:template match="@*|processing-instruction()|comment()">
<xsl:copy>
<xsl:apply-templates
select="@*|node()|processing-instruction()|comment()"/>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>

Invoke it with Xalan (or your XSLT processor of choice). I use the command "java org.apache.xalan.xslt.Process -in stuff.xml -xsl cvtAttrs.xsl" (where the script is in 'cvtAttrs.xsl', of course).

Share and enjoy!