XML myUMBC

The new era has begun…

Today I hacked our legacy myUMBC portal code to generate XML output
for the registration eligibility function. The goal is to eventually
get away from the big, monolithic app and separate the business logic
from the presentation. Registration eligibility was a good place to
start, because (1) it’s a simple function with simple output; (2)
we’ve gotten a request to display registration eligibility within MAP,
so we needed to figure out a way to share the code; and (3)
registration eligibility still talks directly to the HP3000 mainframe,
which makes it impractical to do code sharing via Perl modules,
libraries, etc. So, this is what we came up with. It’s the first
step in coming up with a web-services type API for our back-end SIS
business logic.

The first thing I needed to deal with was the Perl code itself. The
legacy myUMBC code, while nice and modular, is very HTML-centric.
The business logic stuff is all mixed up with the code for HTML
generation. The code works by building data structures to represent
HTML objects, and the business logic bits are all built into those
data structures. Separating this stuff out is not really major
surgery, but it does require some work. I’ve managed to do this for
the registration eligibility code, but I still need to put a run-time
flag into the code to toggle XML vs. HTML output. Also, there will be
the ongoing challenge of dealing with error conditions and generating
valid XML in these cases, but we’re getting there.

On the actual XML front, the main challenge is defining what exactly
you want to output, coming up with a spec (DTD), and sticking to it.
This can require quite a bit of thought, and needs to be done before
any actual code is written. For registration eligibility, the DTD I
came up with is pretty simple:


<?xml version="1.0"?>
<!ELEMENT registration (eligibility)>
<!ELEMENT eligibility (status*)>
<!ATTLIST eligibility status (eligible|ineligible|registered|error) #REQUIRED>
<!ELEMENT status (description,extra?>
<!ELEMENT description (#CDATA)>
<!ELEMENT extra (#CDATA)>

And some sample XML…

<registration>
 <eligibility status="ineligible">
  <status type="academic">
   You are academically ineligible to register.
  </status>
  <extra>
   Your GPA is too sucky for you to continue
   attending the university.
   Please go away.
  </extra>
  <status type="financial">
   You are financially ineligibile to register.
  </status>
  <extra>
   Your parents didn't send us any money this
   semester. Please go away.
  </extra>
 </eligibility>
</registration>

Coupla notes here.. I used the generic tag name “registration” with
the idea that down the road, the object will contain other
registration related stuff besides eligibility. We’ll see if that
actually happens. And, although I do include text-based descriptions
of eligibility status, these are only intended to be guidelines.
Applications can use the descriptions as-is, or key off the “type”
attribute in the “status” tag, and display their own messages.

The code generates this XML using the same Perl objects I wrote to
construct HTML. It seems to work OK for generalized markup, and
guarantees that the output will be “clean”.

Next up is to see how this works when we pull it in as a uPortal
channel. To do this, we’ll need an XSLT stylesheet and we’ll need to
set up a “Generic XML/XSLT Transformation” type channel (rather than a
web proxy). We’ll need to use our custom local connection context
code to connect, and it remains to see how that will work with a
non-web proxy channel. Should be fun..

mp3act

Totally OT… I got my Maryland state tax refund today — 48 hours after I filed with iFile. Can’t beat that turnaround time.

And, I’ve found a promising app for cataloguing and streaming my MP3 collection: mp3act. It’s a web based app that uses PHP, MySQL and Ajax. Setup was pretty straightforward. When I started out I had Apache, mod_php and MySQL already installed and running. It went kinda like this:

  • Download mp3act and untar under my Apache document tree
  • Create mp3act MySQL database:

    mysql> create database mp3act

  • Create mp3act MySQL user by adding appropriate entry to “users” table
  • Edit mp3act config file to tell it how to connect to the new database.
  • Point browser to mp3act config URL
  • Wonder why it doesn’t work. Scratch head.
  • Install PHP4 MySQL module (debian package php4-mysql). Works now.
  • Configure app and import my music library.

Initial impressions: Seems nice, Ajax interface is quite slick. It has a few quirks; for example, if an album has multiple artists (for example, a tribute album), mp3act doesn’t display it as a single album. It breaks everything out separately by artist and album, so if the album has 10 different artists, mp3act displays it as 10 albums with 1 track each. I wonder if there’s a workaround for this; will have to investigate. Also, it won’t import a file without an album tag. This causes some problems with singles, which don’t necessarily have albums associated with them.

Quirks aside, this looks like a great tool and I’m going to continue to play around with it. Supposedly it can use the Amazon.com web services API to download album art too. I’ve signed up for an Amazon.com web services ID so I can try that out. Stay tuned (pun intended)..

Followup: Still playing around with this. It definitely works better when all of the MP3s are tagged properly and consistently, so I’ve been slowly working through the collection doing this. It has another quirk which affects multi-CD sets; if you rip each CD into its own directory, say “Beatles White Album/disc1” and “Beatles White Album/disc2”, and number the tracks separately for each disc, mp3act intermingles the tracks when it displays the album. So, you see disc 1 track 1 first, followed by disc 2 track 1, then disc 1 track 2, disc 2 track 2, etc. One way to fix this would be to just eliminate the separate directories for each disc, comingle all of the tracks, and number them all sequentially. However, I’m wondering how hard it would be to hack the mp3act code to improve its handling of these kinds of albums as well as albums with varying artists. I may take a peek at the code to see how much trouble it would be.

Maryland’s iFile

Well, I filed the taxes this morning, and for the second year I filed the fed taxes electronically with TaxCut, and for the Maryland state taxes I used TaxCut to prepare the return and then filed online with Maryland’s free iFile system. Net cost: $15.95 fed eFile fee – $15.95 H&R Block eFile rebate, + free Maryland iFile, = $0. The iFile system works very well, and the numbers I get from it match the numbers I get from TaxCut, which I find comforting. There are a couple little niggling problems with it; namely, you have to pick a password and then remember it from year to year, for an app that you only use once a year (so of course, I couldn’t remember the password I chose last year, and had to reset it); and the PDF form download they provide doesn’t work with my Mac or my Linux boxes. But aside from that, I’m happy with it, and they claim that using it saves taxpayer dollars. I’m all for that.

On another note, I’m fresh off attending the most exciting college basketball game of my life yesterday, the D.C. Regional final where George Mason upset UConn to reach the Final Four. This is what college basketball is all about, it’s why we buy the tickets, and I’m already lining up to get tickets for next year. And the game had quite a local angle to it, with 70% of the starting 10 players from Maryland. It’s too bad all this talent has to go to out-of-state teams. There are a lot of Division I teams in Maryland (UMBC is one of them), none of them much to write home about. Don’t get me wrong, I love the Terps, but it’d be nice to see some other strong local teams.

IE and XML miscellany

Well, I learned something new about IE today. If you use an empty-element tag in a <script> declaration, for example:

<script language="javascript" src="/foo/bar.js"/>

It breaks IE. Apparently, IE doesn’t treat the trailing slash as a close tag, and treats the rest of the page as an inline script. Firefox, as expected, has no problems with this. The workaround for IE is:

<script language="javascript" src="/foo/bar.js">
</script>

This affects IE 6; don’t know about other versions.

Like everything else around here, we learned this the hard way, after I made some mods to the Student Parking Registration app and it broke for IE users. Fun fun..

On another front.. we’ve got something new coming down the pike. If I can ever get away from all my various menial maintenance chores (FAR, Parking, PlacePro, etc. etc. etc.) we’re going to attempt to do a pseudo-quasi-web services app. It’s not “real” web services, but I’m going to mangle the legacy myUMBC portal code so that various functions can return XML for consumption by other apps. Background: We’ve been asked to display registration eligibility status inside MAP (UMBC’s homegrown kitchen-sink retention-aid-transcript-displaying-advising-scheduling-coffee-making web app). Now, the hitch here is that we currently pull eligibility info directly from the HP3000. It’s all there in the Oracle SIS, but no one has coded up the SQL to fetch it, and our development resources are spread too thin (doing crap like FAR, PlacePro, etc.) to do it right now. And even if we did it, the code would have to be duplicated between myUMBC and MAP, which is just a bad idea anyhow. So, I’m going to hack myUMBC so that MAP can call it, pass some magic params, and read back an XML stream of registration eligibility info (which can be parsed, transformed, etc. as desired). To make this happen, we need to do a few things..

  1. Move MAP off its current SGI hardware and onto a more modern platform, where we have the LWP::UserAgent and Crypt::SSLeay Perl modules installed
  2. Figure out a workable DTD for the XML data, and stick to it
  3. Modify the legacy myUMBC code so it can output XML (via a custom URL, custom CGI parameter, etc.
  4. Modify MAP to call this routine using LWP::UserAgent, parse the results, and display them

Like I said, not really “web services” in the official sense, but the same kinda thing. This seems like an easier, and possibly better, way to do code sharing without having to do major surgery on the legacy code. As with everything else, we’ll see how it works in practice.

The Eternal PlacePro Struggle.

Well, I spent most of the day fixing our PlacePro single signon. Again.

PlacePro is the ultimate example of why I hate doing single signon to remote webapps on third-party sites. It’s a pain doing it in the first place, and when it’s done, it becomes an ongoing maintenance hassle. When it works, it’s nice, and one could make an argument that it enhances the end-user experience. But when it breaks, you end up reinventing the wheel to make it work again. And of course, it always breaks at the most inopportune times. My opinion: If you’re not going to do it right, don’t do it at all, and spare everyone the hassle. But anyhow..

PlacePro is an online service that allows students to post resumes for consumption by potential co-op and internship employers. They do this in conjunction with The Shriver Center. I’ve been dealing with PlacePro for, oh, 4-odd years or so. The usual iteration goes something like this:

  1. We set up a single signon from myUMBC into PlacePro’s system. It works and the world is happy for, oh, a year or so.
  2. The PlacePro people make a change on the back-end that breaks the single signon.
  3. I get a frantic call from the Shriver Center.
  4. I talk to the tech guy at PlacePro, and tweak the single signon process to work with whatever changed on their end.
  5. Go to 1.

We’ve been through this loop 3 or 4 times now. The good news is, it now looks like UMBC is going to be dropping PlacePro. The question is, will the new vendor be any better? Time will tell I guess.

Basic PlacePro workflow, for anyone who wants to commiserate:

  • A student decides they want to use PlacePro to find an internship placement. The student goes to the Shriver Center, and the Shriver Center staff enters the student into PlacePro’s system using the PlacePro Coordination Module.
  • As part of this process, we put the student into a database table on the Oracle shadow, AUXIL.PLACEPRO_ACCESS. The table is indexed by student ID, and includes an access code field. NOTE: As of 3/22/2006, this access code is NO LONGER USED. This table is now used SOLELY to determine whether to display a PlacePro link to the student in myUMBC. If the student’s ID is present in the table, the student sees the link; if not, not.The script that puts the students in the table lives at: http://my.umbc.edu/cgi-bin/placepro/addlink.pl
  • The student should now see a PlacePro link in myUMBC. The link is currently generated by the legacy Perl myUMBC codebase, in the file /myumbc/src/myumbc/placepro.pl. This code builds a link that posts the student’s ID and last name to https://my.umbc.edu/placepro.jsp. The JSP opens in a new window, and displays some verbage from the Shriver Center as well as a link to the actual PlacePro site. To generate this link, we run the student ID and last name through an encryption routine provided by PlacePro. All of this happens in the JSP. The last name does not seem to be case sensitive.
  • The student clicks on this link, and is automatically logged into the PlacePro system (until it breaks again).

That’s about it, for now. Oh, one last caveat: The student’s last name in the PlacePro system MUST match the last name on file in SIS. Otherwise, this will pass an incorrect last name, and the signon will not work. Students in this situation should be referred to the Shriver Center. The change can be made via the PlacePro coordination module.

Garage Door Opener

Well… first off, I’m back from my most excellent trip to Philly to watch first and second round action from the NCAA Men’s Basketball tournament. This year’s games were great, and it’s always nice to get away from the grind for a few days. It always butts up against UMBC’s Spring Break, so I get a couple extra days off afterwards to boot. And, this year I have tickets for third and fourth-round games in DC, with the overall winner going to the Final Four. That’s coming up this weekend. Oh, and it’s t-minus 9 days and counting until the official due date for our second rugrat. My pulse quickens as I type (in spite of the beta blockers).

Anyhow, this evening our garage door opener decided to go on the fritz. We were all sitting around in the dining room twiddling our thumbs, when the garage door decided to open all by itself! Always fun. I went and checked it out. The remote receiver module is a separate unit, and as I fiddled with it, the opener tripped a few times. I could hear a relay clicking somewhere each time it tripped. Shrugged my shoulders, went back inside, closed the garage door. Halfway through closing, it stopped. Tried again, it closed this time. An hour or so later, it opened itself again, and stopped midway. Seems that it’s become sensitive to vibration or something. I unscrewed the receiver from the unit and pulled it off. It’s a nice, cheapo, made-in-China piece of garbage just like everything else these days. With the receiver off, so far, it hasn’t acted up. So I’m tempted to blame the receiver. First, though, I’ll try reattaching it and changing the code, just to see what happens. If my suspicions are confirmed, and the receiver is bad, I’ll look into getting some keyfobs for our alarm system, and programming them to work the opener.

Nice that it decided to do this while we were home and around, rather than away with the alarm set, etc..

Linux and IDE drives

I stuck a scavenged 120-gig IDE hard drive in my desktop Linux box at work. For now, it’ll house my MP3 collection, which is rapidly outgrowing the 35-gig partition it had been living on. My eventual plan is to get a couple of large (say, 300 to 350 gig), identical drives, keep one at work and one at home, and use them to house all of my MP3s, digital photos, etc. as well as backups of all my machines. I’d keep the disks synchronized with unison or something similar, and then I’d have my data replicated in two locations. But as usual, I digress.

As I was copying my MP3s over to the new drive, I got a few happy-fun-ball I/O errors in my kernel log:

Mar 14 13:36:00 sonata kernel: hda: dma_intr: error=0x40 { UncorrectableError }, LBAsect=7374122, sector=7084952
Mar 14 13:36:02 sonata kernel: hda: dma_intr: status=0x51 { DriveReady SeekComplete Error }
Mar 14 13:36:02 sonata kernel: hda: dma_intr: error=0x01 { AddrMarkNotFound }, LBAsect=7374122, sector=7084952 Mar 14 13:36:04 sonata kernel: hda: dma_intr: status=0x51 { DriveReady SeekComplete Error }

Repeated 20 or 30 times.

Odd thing is, the errors were on my main disk (hda), not the scavenged disk (hdb). The main drive has never had a single issue before. That makes me think “kernel issue” more so than “bad disk” (running 2.4.31). I googled around a bit, and found some (admittedly a bit dated) advice to turn off the CONFIG_IDEPCI_SHARE_IRQ and CONFIG_IDEDMA_PCI_AUTO flags. Tried this, but it made the machine really sluggish whenever there was disk I/O, and all of a sudden I started having problems ripping CDs. So I turned CONFIG_IDEDMA_PCI_AUTO back on, which sped things back up made ripping work again. So now, I guess I need to keep an eye out for I/O errors again. When I was getting them, I was copying 20+ gigs of data from the master IDE drive to the slave IDE drive. Ever since I stopped doing that, I’ve had no problems on either drive. We’ll see I guess..

Two-Speed pool pump motor: Worth it?

With electricity prices set to skyrocket in the Baltimore area this summer, I’m once again looking at ways to cut down on our consumption. And in the summer, one of our biggest consumers is the pool pump. I’ve read that a two-speed pool pump motor can cut down quite a bit on energy usage. A standard swimming pool pump runs at 3450 RPM. A two-speed pump can also run at half speed, or 1725 RPM. The interesting thing is, although it’s running at 50% of normal speed, a typical model draws less than half (about 30%) of the current that it would draw running at full speed. So in theory, you could run the pump at half speed for twice the length of a normal-speed run cycle, do the same amount of work, and use less power. Now, this might be wrong. I’m not well-versed enough in fluid dynamics to say whether, for instance, 10 hours at 1725 RPM would turn over the same amount of water as 5 hours at 3450 RPM. But, for the sake of argument, let’s assume it would. Running at low speed would certainly consume less power, and longer pump run cycles are good for the pool water because they keep it from stagnating (I’ve also heard anecdotal reports that sand filters “work better” with lower flow rates — take that for what it’s worth). Of course, that’s only one side of the story. There are also some caveats..

  1. I can’t run the pump on low speed 100% of the time. I have a pressure-side pool cleaner (Polaris) with a booster pump, and I’d need to run at full speed while the cleaner was on (3 hours a day, 3 days a week or so). And, I’ll want to run on full speed for 3 hours or so after adding chemicals, to get them distributed as quickly as possible. Any time the pump is on high speed, it cuts into my potential savings.
  2. The pump motors I’ve looked at all consume somewhat more current (1 amp or so on models I’ve looked at) on high speed, than equivalent single-speed motors. Not quite sure why this is, but it seems to be a fact of life.
  3. Initial cost for a two-speed setup is high, even assuming I’m only replacing the motor and not the entire pump. The pump would need special wiring, and a new timer/control to control high vs low speed operation. Plus, I’d need a new shaft seal. I’m probably looking somewhere in the $350-$400 range when all is said and done.

A couple years back I made a spreadsheet to estimate potential savings using a two-speed pump, and concluded that the payback period was too long to justify the initial effort and expense, given what I was paying for electricity at the time. However, with the pending rate hike, it’s probably worthwhile to crunch those numbers again..

Web Proxy channels and cw_person parameter

CWebProxy provides a channel parameter called cw_person, which is a comma-separated list of person attributes. If this parameter is set, CWebProxy is supposed to fetch the listed attributes and pass them to the back-end web application as CGI parameters. This is a potentially handy feature, because then we can develop “smart” unauthenticated apps which present content tailored to individual users. For example, in our case we’d like to develop a “campus links” channel, which presents different sets of links to different users based on their LDAP affiliations. In theory, with cw_person, that should be easy to do.

Well, I tried this out, and as usual, what sounds great in theory isn’t always great in practice. There are two issues with this feature:

  1. If a user has multiple values for a given attribute, CWebProxy only passes one of them (presumably the first one returned by the LDAP query). Example: I set up a test channel with cw_person set to pass the LDAP affiliation attribute. My LDAP affiliations are “staff”, “employee”, and “alumni”, but CWebProxy only passes “staff”.
  2. When the channel is refreshed (e.g. by switching to a different tab and then going back), it seems to stop passing the attribute. Not sure if this is user error, or if that’s just how it works. But if this is going to be any use to us, it needs to pass the attributes every time the channel is rendered.

These two problems will probably prevent us from using this feature to do what we want. Yeah, we can probably hack CWebProxy to make this work, but I think a better solution would be to write up a custom local connection context. That will give me complete control over what gets passed to the back-end app (and when), with the added benefit that we can use it with a CGenericXSLT type channel and aren’t limited to using a web proxy.

Incidentally, for anyone trying to set up a channel with cw_person, there’s one big “gotcha”. There is an additional channel parameter called cw_personAllow. This parameter is a list of attributes that the channel is allowed to pass to the back-end app. The default is to disallow every attribute. So if you’re like most people, you’ll set up cw_person, ignore cw_personAllow, and then wonder why it doesn’t work. To get it to pass your attributes, you can either set cw_personAllow to ‘*’ (meaning “pass any attribute”), or specify an explicit list. This can be done in the channel definition, but there’s also a global default in portal.properties called org.jasig.portal.channels.webproxy.CWebProxy.person_allow. Yes, this is all in the CWebProxy documentation, but you have to dig for it. A tutorial would probably help.

Tractor deck…

I put the deck back on the tractor yesterday, and I’ve got the skinned knuckles to prove it.

Every fall, I take the deck off the tractor. The sole purpose of this is so I can take the blades off to sharpen them. Now, it’s really, really easy to take the deck off the tractor, and it’s really, really hard to put it back on. But, because I take it off at the end of the mowing season, I can wait 4 months to put it back on. Of course, the 4 months go by quickly, and before I know it, I’m out putting the deck on, skinning my knuckles, and cursing up a storm.

My tractor was a freebie that came with the house. I really can’t complain too much about it, because it runs pretty well. It’s a “Powr Kraft”, which is the Montgomery Ward house brand, which should give you an idea of its age. Of course, like every other house brand, it’s built by MTD. My theory is that MTD tractors are made by guys who hate tractor mechanics. There are about 15,000 little lifter arms and cotter pins that attach the deck to the tractor. To put the deck on, you first have to slide it underneath the tractor. Well, while you’re doing this, you have to keep moving the little lifter arms out of the way, because they are always catching on the pulleys, belt, etc. Then, you have to perfectly align the deck with the tractor, and slide this metal rod through both of them. But, it’s hard to do this because the belt is in the way. Etc. etc. etc.

This year, I tried jacking the tractor up, and it did make it easier to slide the deck underneath. But, I couldn’t set the tractor back down on the deck, because the 15,000 little lifter arms kept getting wedged on stuff. Net result: Jacking the tractor doesn’t make it any easier.

One of the reasons I keep putting myself through this, is that I only have to do it once a year. After a year, I tend to forget what a pain it is. Remember the old adage, “Time heals all wounds?”

If I could figure out how to remove the blades with the deck still on the tractor, I wouldn’t have to do this any more. Problem is, I’d have to get underneath the tractor with my impact wrench somehow. That would mean raising the tractor about 2 feet off the ground. Then I’d have to replace the blades from underneath too. Somehow, that sounds like just as much of a pain as removing/replacing the deck. But, it’s worth trying once, if I can figure out how to do it. Maybe this will be the year..