- Run 'route -n'
- Ensure there's an entry that looks like the following :
- Destination: 0.0.0.0 Gateway: [the desired gateway, e.g. 192.168.10.1] Genmask: 0.0.0.0 Flags: UG, Metric: 0, Ref: 0, Use: 0, Iface: eth0 (or eth2, or whatever you may have)
- If the above entry doesn't exist, add it with the following command:
- sudo route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.10.1 dev eth0
Thursday, January 24, 2013
"Network is unreachable" on an embedded box
We've been working extensively with embedded boxes lately trying to migrate our platform over. One of the programs we've been trying to migrate today was a simple announcer program that sends out a UDP broadcast. On our first go, the program wouldn't work, saying that the "Network is unreachable" when doing a 'perror' after the call to 'sendto'. After much experimentation with various network binding flags, etc, which took the whole day, I went into overtime trying to figure out the problem. Eventually, I got to the point where I inspected the routing tables of the embedded system and was shocked to find .... there was no routing table entry for the default gateway, i.e. 0.0.0.0 destination IP. I'm actually kind of ashamed of how long it took me to find this, but in my defense, I was dealing with a wild mixture of systems and had done a lot of network reconfiguration with the systems and some of the stuff was handled automatically for me by my Ubuntu box, which gave me some inconsistent results at times when experimenting. For my own future reference, here's how I solved the problem:
Friday, January 18, 2013
Synchronizing with a remote SVN repository
We recently had a double hard-drive failure on our RAID 5 on our main server at work, and we lost our stuff. Our primary backup method of that system had completely failed due to the incompetence of some of the staff in our parent company that was completely beyond our control. It's only by the grace of the fact that our IT guy had an extra weekly backup that we were able to recover as much as we were. That said, it brought to light the need to have our own extra backups of our source code because we can't rely on the people we're supposed to be able to rely on to do the backups for us. We're now going to be creating our own copies of the SVN server on a semi-daily basis, and I've found instructions on how to do it on this awesome StackOverflow post. I'm just sorry I didn't know about this sooner.
Cross-compiling libexpat (and other libraries)
So, lately I've been trying to cross-compile libexpat for a new board with a different architecture from what we typically use at work, and I've been having one heck of a time, until I finally got help online from my question at stackoverflow. Turns out, I just needed to be more specific about my host architecture in the configure command:
./configure --host=arm-none-linux --enable-shared CC=arm-none-linux-gnueabi-gcc CXX=arm-none-linux-gnueabi-g++ AR=arm-none-linux-gnueabi-ar RANLIB=arm-none-linux-gnueabi-ranlib STRIP=arm-none-linux-gnueabi-strip
I'm going to try similar recompilation with other libraries so that I can improve the functionality of our older systems as well.
./configure --host=arm-none-linux --enable-shared CC=arm-none-linux-gnueabi-gcc CXX=arm-none-linux-gnueabi-g++ AR=arm-none-linux-gnueabi-ar RANLIB=arm-none-linux-gnueabi-ranlib STRIP=arm-none-linux-gnueabi-strip
I'm going to try similar recompilation with other libraries so that I can improve the functionality of our older systems as well.
Tuesday, January 15, 2013
Journey to robust Windows Services
Because I have the need for it in several projects, I'm going to be working with Windows Services implemented with .NET quite a bit in the next little while. That said, I'd like to refer to some resources for working with Windows Services:
- The MSDN page on creating windows services
- The MSDN page on debugging a Windows Service
- The MSDN page on installing and uninstalling Windows Services
Also, there are some issues that I ran into while working on my first service. I had originally planned to expose a WCF endpoint through WS-HTTP, however, I can into an unpleasant exception when I tried to do so: AddressAccessDeniedException. Apparently, the cause of this is the fact that http bindings are reserved for processes run as administrators. Fool me once, shame on me. In light of that fact, I'm switching over to using net.tcp for my local Windows Service-hosted WCF service. I'll post more here as I learn it.
Adding the Network Service account to the permissions for a file/folder/registry key etc
I'm currently developing a Windows Service that's to be run under the Network Service account (owing to the fact that it requires substantial network access). I'm just starting to learn how to program Windows Services, so this is very new to me. I just started developing a simple service to start, and when I followed the instructions on this MSDN page, I was surprised when, after trying to start my service, I got the error 005: Permission denied. I later found out that I needed to give the network service account to the folder from which the service was running, i.e. the debug output folder of my project. Doing this isn't as simple as setting the permissions for other users. To add the network service account to the permissions list in Windows 7, do the following :
- Right click on the folder containing the service you want to debug, and go to Properties
- Go to the Security tab, and under the box marked "Group or user names", click on Edit ...
- Under the window that pops up, you'll see another box marked "Group or user names". Underneath that box, click on Add ...
- In the "Select Users or Groups" dialog that pops up, click on the Advanced... button at the bottom left.
- In the advance "Select Users or Groups" dialog that pops up, click on Find Now to find all the users, groups and built in security principals.
- Once the search results are populated, scroll down and select "NETWORK SERVICE" (not "NETWORK") and click on Ok, then Ok again in the Select Users or Groups dialog.
- One you're back to the Permissions window, ensure that you give the Network Service account Full Control over the folder in the Permissions box, then click Ok.
- Click Ok one last time to close the properties for your folder, and you're done.
Sunday, January 06, 2013
Wpf2WinRT: Bindings are not the same!
I've just learned something new about the bindings system in WinRT: they're not the same as Bindings in WPF. Check out the MSDN page for WinRT BindingMode, and you'll see that they're missing a value from WPF: OneWayToSource.
Migrating from WPF to Windows 8
As part of the ongoing development efforts at my company, I'm always researching the latest technologies and developments. My latest endeavour is researching Windows 8 and doing technical feasibility work to find out if it's right for us. On that front, I'm learning how to develop for the Windows 8 runtime in C#. I've got a lot of experience in WPF, and our current Line-Of-Business application is written in WPF, so I'm used to certain things, things which I'm finding no longer hold true in WinRT programming. For example, how resources such as strings are accessed in XAML. For WPF, if I wanted the internationalized string for a label, I'd do something like this :
<Label Text="{x:Static resources:Messages.UserName}"/>
However, the x:Static XAML extension no longer exists. Instead, you have to follow the *very different* ways of accessing string resources on this MSDN document, because the means of accessing resources for a WinRT application are both simpler (in some ways) and more robust, if a little bit confusing at first.
For string (and even other resources, such as images) internationalization in WinRT, what you do is this:
1. Create a folder path for your strings: \Strings\en-US
2. Under the aforementioned folder, create a resources file named Resources.resw (not that it's not .resx, as with previous .NET applications written in Windows Forms, WPF and ASP.NET)
3. Add a new string with the Name "ApplicationName.Text". The ".Text" suffix is very important; you'll see why in a minute.
4. In your XAML, create a TextBox like this:
<TextBlock x:Uid="ApplicationName" Text="" />
The Uid attribute is used for associating controls with resources, according to the MSDN link provided above, and the .Text suffix in the resource Name column, specifies the property to which the resource is linked.
Honestly, I'm not sure I like the way this is going, but if it reduces code clutter, I'm willing to at least give this a try. We'll see how this pans out.
Wednesday, December 19, 2012
Finding the public key and public key token of an assembly for use with InternalsVisibleTo
It seems like one of the most common problems out there when it comes to testing and dealing with strongly-named assemblies is getting the public key and the public key token of a CLR assembly when you want to create test projects for that assembly. The quick and easy way:
1. Ensure that 'sn.exe' is in your path. If it's not, it's usually in the Windows SDK directory, typically in a folder like : C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\sn.exe
2. Open up a command window, and navigate to the directory containing the snk you're signing your assemblies with.
3. sn -p my.snk Public.snk
4. sn -tp Public.snk
...and voila, the program will print out the public key and public key token to the screen for you. The reminder I found this at was here. After you've found the public key, you'll want to paste a line similar to the following in the AssemblyInfo.cs file of the project whose internals you wish to expose:
[assembly: InternalsVisibleTo("MyCompany.ProjectName.Tests,
PublicKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")]
Changing the default URI of a WCF web service
I recently returned to one of my previous employers since they lured me away with a better employment offer than I was currently receiving. I'm now placed on a large scale project to revamp our equipment and our software, and improve our software services to the point where we have absolute control and communication with our software out in the field. To that end, I'm presently writing a service that our field personnel's laptops will use to communicate with our home office. I've decided to implement the service in WCF since I've got experience with it and it will be the quickest route to getting a working service deployed. There are other reasons for the choice, but we won't go into those at the moment. One of the things I had forgotten about was the best practice of changing the default namespace of the service away from tempuri.org. I found an article here to remind me of how to do it, so hopefully others will be able to find it as well.
Sunday, September 16, 2012
Previews with a DataContext in WPF
Both the company I currently work for and the previous company I worked for use WPF extensively in some of their products. I recently ran across an incredibly helpful bit of functionality that aids in designing controls and forms on this blog. The TL;DR is that it lets you specify the data context that's supposed to be associated with a window / control, and lets you preview the control at design time using design data. This is useful because it lets you get an idea of what the control is going to look like with real data, and can be especially helpful when trying to size controls on a form, or seeing what lists are going to look like, and will be a real time saver because it means you no longer have to compile and start the application in order to view styles on things like DataTemplates.
Sunday, June 26, 2011
Deleting more Subversion folders
Further to my previous post on deleting subversion folders with powershell, I recently had the need to do the same in bash on linux. Here's a useful little command :
find . -name .svn -exec rm -rf {} \;
Wednesday, October 13, 2010
Determining what ports Tomcat is running on
I've recently had problems connecting to a Tomcat server setup by another developer. In order to troubleshoot these problems, I wanted to use netstat to see what ports were being bound to, but apparently server sockets don't come up by default. If you really want to see what server sockets are in use on your machine, use the following :
The undocumented 't' option will cause netstat to show server sockets.
netstat -lntp
The undocumented 't' option will cause netstat to show server sockets.
Friday, October 01, 2010
Event models in Silverlight vs WPF
To really understand the event model in Microsoft's Silverlight / WPF frameworks, you need to start off with a proper mental model. You can think of the root XML element in a XAML object as being at ground level. Each successive child XML element goes deeper into the ground. With that in mind, there's two terms that are used in both of these frameworks to describe how event handlers propagate between objects : Bubbling and Tunneling. With Tunneling, event handlers start at the root XML element, and "tunnel" deeper into the "earth" (your control stack) until they get to the original source of the element, which is your controls. With Bubbling, event handlers start at the original source of the element (your control which initiated the event), and then "bubble up" to the root control. We use the earth analogy because the terminology goes hand in hand with gravity : tunnelling follows gravity, like digging into the earth, and bubbling goes against gravity, like a bubble coming up to the surface from the bottom of the ocean. I never really had a clear mental model of the Silverlight / WPF event models until right now.
With all that said, there are some differences between Silverlight and WPF that turn out to be very important when it comes to implementing your event handlers, and maintaining compatibility between the two. The biggest difference is that WPF supports both Bubbling and Tunneling events, whereas Silverlights supports only Bubbling events. Keep this in mind if you're designing desktop applications that you might want to port over to web applications at some point
With all that said, there are some differences between Silverlight and WPF that turn out to be very important when it comes to implementing your event handlers, and maintaining compatibility between the two. The biggest difference is that WPF supports both Bubbling and Tunneling events, whereas Silverlights supports only Bubbling events. Keep this in mind if you're designing desktop applications that you might want to port over to web applications at some point
Thursday, September 30, 2010
Holy crap, a Windows ramdisk program that works
In the past, I've had a bit of experience trying to install and run ramdisks in Windows XP, but I never found anything really good. Microsoft provided a ramdisk driver for Windows 2000 that one could get working in XP, but it wasn't really useful because it only provided a maximum of 32 MB of storage (Why ?!). I decided to revisit the issue of ramdisks at work today for Windows 7 because I wanted a way to speed up Visual Studio's caching and other operations (and Eclipse, but that's another matter). After searching around some more, I found a program called ImDisk. The installation is dead easy (if a little lacking in the notification department to tell you it's successfully installed). What's even better, you can make disks of arbitrary size, have it simulate various kinds of devices, and you can setup multiple ramdisks. The only catch is that you have to start the service in Administrator mode, and it's a bit more than trivial, though it is easy using the following steps :
1. Start -> All Programs -> Accessories -> Command Prompt -> Right-click -> Run as administrator
2. sc config imdisk start= auto (note the space between start= and auto, this got me the first time)
3. net start imdisk
4. Open up Control Panel -> ImDisk Virtual Disk Driver
... and have at it!
1. Start -> All Programs -> Accessories -> Command Prompt -> Right-click -> Run as administrator
2. sc config imdisk start= auto (note the space between start= and auto, this got me the first time)
3. net start imdisk
4. Open up Control Panel -> ImDisk Virtual Disk Driver
... and have at it!
Friday, September 17, 2010
Editing your file system mappings for TFS paths
So, as you may or may not know, when using Microsoft Team Foundation Server for version control, TFS maps remote project paths into local file system patmhs for checkout, etc. As I learned today, there are times when you check out the wrong path, and/or map it to the wrong path in the file system. If you ever need to modify or just nuke your file system mappings in TFS, here's how you go about doing it :
Once you've selected your workspace in the dialog that comes up (you'll likely only have one anyway), click on :
... and then select the folder to file system mappings that you want to remove, or create whatever new file system mappings you want right there.
Team Explorer -> Source Control (double click) -> Workspaces (dropdown) -> Workspaces ...
Once you've selected your workspace in the dialog that comes up (you'll likely only have one anyway), click on :
Edit ... -> Working Folders
... and then select the folder to file system mappings that you want to remove, or create whatever new file system mappings you want right there.
Starting a new job ... and a new philosophy
Ok, so I've left my previous employer and started at a new job. This means new domains of knowledge, new tools, and new people. My new employer is a Microsoft-exclusive shop, for almost every aspect of their software. If you've read this blog in any significant amount in the past, you'll know that I'm really not a Microsoft fan. In fact, I hate almost everything that's ever come out of Redmond, because for the most part it's deficient in how it's been engineered, and not as usable as other products out on the market (or even a lot of open source products). Therefore, the tone of this blog is probably going to change somewhat, and I'll be ranting and raving like a lunatic on things I'm learning about dealing with Microsoft products more often. It's going to be interesting.
Thursday, September 02, 2010
Finally ... MySQL workbench sucks less
MySQL workbench has been out for quite a while, under the guise of the people at MySQL. For the longest time, I stuck to using just the individual MySQL Query Browser and Administrator because they weren't too bad, and there really wasn't anything out there that I liked much better for query browsing alternatives. I tried out the old Workbench back when MySQL was standalone, but it really wasn't a very positive experience, so I just dropped it.
However, lately, something drove me to search for better alternatives to the MySQL query browser again, and I don't even know why. In my Google search, the MySQL Workbench came up, and I saw that it was a very recent version that was a good .2 versions up from the last one I had used, so I figured I'd give it a try. The difference was startling. Not only did they completely revamp the interface (at least for the Mac) but the workbench was just generally much more reliable and performant than the old query browser. If you get the chance, give it a shot. The new integrated interface is much more user friendly, and there's a bunch of new "Copy to clipboard" snippets that I personally find incredibly convenient and useful.
However, lately, something drove me to search for better alternatives to the MySQL query browser again, and I don't even know why. In my Google search, the MySQL Workbench came up, and I saw that it was a very recent version that was a good .2 versions up from the last one I had used, so I figured I'd give it a try. The difference was startling. Not only did they completely revamp the interface (at least for the Mac) but the workbench was just generally much more reliable and performant than the old query browser. If you get the chance, give it a shot. The new integrated interface is much more user friendly, and there's a bunch of new "Copy to clipboard" snippets that I personally find incredibly convenient and useful.
Monday, August 16, 2010
Running multiple Tomcat 6 instances in Ubuntu, the quick and dirty way
Here's a quick list for running multiple Tomcat 6 instances on Ubuntu 10.04 :
You should now have a running, fully functional instance of Tomcat on the server, using a different port.
- Copy the /etc/init.d/tomcat script with a new name in the same directory
- Update the NAME variable in the startup script with a name for the new instance that you want to run.
- Copy /usr/share/$(old)NAME to the (new) NAME you've just created, along with /var/lib/$(old)NAME and /etc/default/$(old)NAME
- Edit the server.xml file under /var/lib/$(new)NAME/conf/ and change all the ports (ie for shutdown, and all your Connectors) so that they don't conflict with the old instance
- Run /etc/init.d/$(new tomcat script name) start
You should now have a running, fully functional instance of Tomcat on the server, using a different port.
Wednesday, August 04, 2010
Quickly dropping all the tables in a MySQL database without dropping the database itself
I've recently come across a case where I need to drop all the tables in my database (ie effectively truncate the database) but MySQL has no built in command for doing so. This is where the magic of command lines becomes very useful. I found a great little trick here that will very quickly let you get rid of all that annoying data so you can load in new test data into your database :
If you've got GnuWin32 or another set of GNU programs installed on your Windows box, you can even do this in Windows without even changing the syntax !
mysqldump -u[USERNAME] -p[PASSWORD] --add-drop-table --no-data [DATABASE] | grep ^DROP | mysql -u[USERNAME] -p[PASSWORD] [DATABASE]
If you've got GnuWin32 or another set of GNU programs installed on your Windows box, you can even do this in Windows without even changing the syntax !
Tuesday, August 03, 2010
Quickly dumping a MySQL database out to a file
I've found that sometimes, I just need a quick and dirty copy of a database to test changes against, and it doesn't matter if the data is recent, or even consistent for that matter. That's where mysqldump comes in handy with the --single-transaction option. It can be used on a live database because it doesn't lock the tables and prevent your web application from continuing to insert, modify and delete new records. One example would be :
This can be made even quicker by combining this dumping into an SSH transfer to copy the output data to another machine.
mysqldump -u myusername -h myhost -p --single-transaction mydbname | gzip > mybackupfile.20100803.sql.gz &
This can be made even quicker by combining this dumping into an SSH transfer to copy the output data to another machine.
Piping a MySQL database from one server to another
I know that there's a lot of great, wonderful things that can be done on-the-fly through SSH. It's one of the greatest tools out there for moving data or communicating between two machines. So I thought "Why not try to move my database in the fastest way possible via SSH?", and here's the command I found :
This is of course a very basic, stripped down version of the command, which I haven't tested yet, but it's a good start to what seems to be a very common problem among developers.
mysqldump -ux -px database | ssh me@newhost "mysql -ux -px database"
This is of course a very basic, stripped down version of the command, which I haven't tested yet, but it's a good start to what seems to be a very common problem among developers.
Converting Unix timestamps to something readable in Excel
Our company uses log files in our machines to log raw data in real-time so that we have a history of what's happened on jobs that can be analyzed and, if necessary, used to prove to inspectors that our machines are doing what they say they are. We have our own log file parsing and analysis package for performing various analysis, but sometimes we need to look at the raw signals themselves in the logs, and this is where Excel comes in handy. The timestamps that are put on the signals are Unix timestamps, so seconds since the Epoch (Jan. 1, 1970 at 00:00:00 hours), which are not very useful in Excel, because Excel has its own Epoch (Jan. 1, 1900). To make things just a little bit more complex, we have machines in different timezones, which we need to account for as well. Thanks to a post at this site, I managed to come up with a modified version of their formula that will convert the unix timestamps to Excel format, and adjust them for timezone offsets :
=(ROUNDDOWN(A10116,0) / 86400) + 25569 - TIME(6,0,0)
Thursday, July 15, 2010
Coding for Effect vs Coding a Model
One of the software engineers I've worked with in the past has a very different way of doing things. He will take the most expedient way of implementing a particular piece of software in order to get the job done, always. Every time. (Admittedly, there is the odd time where this is the most effective means of doing software in order to get a job done, and potentially avoid getting fired, but that's for another discussion). The person in question takes no pride in his own work (by his own admission) and doesn't care about the quality of his work. Essentially, he codes with only the desired effect of his work in his mind. I've found that this way of doing things (while being faster) is a great way to introduce bugs and to cause problems later that arise as a lack of planning and forethought, ie lack of maintainability, inability to add features to the software.
Whenever I write my own software, I write it to model what's going on in a business or scientific process. (Really, isn't that what software's supposed to do ? ;P) I think about what's going on, and I try to model it in the software, taking into account all possible factors (or at least everything that reasonably occurs to me) that may influence the process. This typically results in a reliable system that very rarely breaks down. When a breakdown does occur, it's always been the result of something that I hadn't anticipated. I then go back to the software, and re-evaluate the model to see if there's something I need to change, and change it. This way of implementing software results in software that's easy to maintain, is (virtually) free of side effects, and can virtually eliminate undesired behaviour.
Whenever I write my own software, I write it to model what's going on in a business or scientific process. (Really, isn't that what software's supposed to do ? ;P) I think about what's going on, and I try to model it in the software, taking into account all possible factors (or at least everything that reasonably occurs to me) that may influence the process. This typically results in a reliable system that very rarely breaks down. When a breakdown does occur, it's always been the result of something that I hadn't anticipated. I then go back to the software, and re-evaluate the model to see if there's something I need to change, and change it. This way of implementing software results in software that's easy to maintain, is (virtually) free of side effects, and can virtually eliminate undesired behaviour.
Monday, July 05, 2010
Working with CSV files in Excel the way you want ... rather than the way Microsoft tells you to by default
In both my current job and my past jobs, I've worked with CSV files in Microsoft Excel as a matter of necessity. Excel is just too useful not to use (OpenOffice has its own quirks, but that's for another post.) The only problem with Excel is that it formats fields automatically when you open a CSV file by double clicking on it or by going through the File -> Open option (or Ribbon -> Open in Office 2007 and later). Sometimes this is nice, but most times it's a pain in the ass, especially when you want to be able to save that CSV data right back out to CSV again. What happens is Excel converts the values to particular data types after introspecting the data in the cells. This is annoying and stupid, especially when you have financial, scientific and engineering data that's in a format that doesn't fit will into Microsoft's algorithm for dealing with numbers, dates, times, and currencies.
There is a way around this giant annoyance. Instead of taking the easy way of opening the file, you can open Excel directly, with a fresh spreadsheet. Then, on the ribbon, go to the Data tab, and click on the 'From Text' button. This will let you open a delimited text file, and treat it as a data source, and Excel will give you options for opening the file and how you want to deal with its contents. This is much better for dealing with the data, especially when dealing with tab-delimited files.
There is a way around this giant annoyance. Instead of taking the easy way of opening the file, you can open Excel directly, with a fresh spreadsheet. Then, on the ribbon, go to the Data tab, and click on the 'From Text' button. This will let you open a delimited text file, and treat it as a data source, and Excel will give you options for opening the file and how you want to deal with its contents. This is much better for dealing with the data, especially when dealing with tab-delimited files.
Thursday, June 10, 2010
Background on the home search page ? WTF Google ?
Today I went to Google (as I do most days) to search for some stuff, but today when I got there, I was greeted with a horrible looking background on the search. My very first thought was "Did I just go to Bing by mistake ?", then I looked closer at the page and realized it was indeed Google. Needless to say, I was incredibly pissed. Why did Google change their signature search page ? It was great the way it was ! That was specifically why I went to Google. If I wanted to use a search engine that looked like a piece of shit, I'd use Bing. In my mind, this is nothing short of an epic fail on Google's part. And apparently, I'm not alone in this feeling.
Wednesday, May 05, 2010
My hatred of Microsoft is justified ... yet again
There are two different schools of thought when it comes to being a vendor of very large software used by millions of people :
I've noticed from my own personal experiences that Apple tends to take the former attitude, whereas Microsoft tends to take the latter attitude. In the end, someone (probably you) is going to get screwed over at some point, it's just a matter of how you want it to happen. As a consumer, I generally like Apple products, so on my personal technology front, I choose the former. As a software developer, I'm forced by business constraints to accept the latter. The specific circumstances that bring me to mention this :
Today I was working with version 4.0 of the .NET framework, the very latest (and supposedly greatest) from Microsoft, along with the very latest version of their Visual Studio (2010) software. I'm also using these with WPF (the Windows Presentation Foundation) to create an application for my employer. For various reasons, I had to go back and refactor some old code, part of which required opening log files for viewing within the application. In order to open a file in WPF, you must use the OpenFileDialog class that comes with the Windows Forms library, which has been around since Windows 2000 (if not earlier, I'm admittedly not as familiar with the lifecycle of this technology as I could be). If you're a developer on the Windows platform, or even just an observant user, you'll have noticed that on the OpenFileDialog there's a Places bar on the left hand side of the dialog that provides common places for storing files, some of which are very general and will require some drilling down into subfolders to find what you actually want. I wanted to be able to add a place to this bar so that users of the application (company employees who work as technicians out on job sites) could have a direct link to the typical directory used to store log files on their machine.
It turns out that adding a folder to the places bar is obscenely difficult, and must be done through registry hacks. My question is this (directed squarely at the developers at Microsoft responsible for writing the GUI controls, specifically these dialogs) : what the hell was going through your head that would make you think that the way you've implemented these dialogs is a good thing ? It's obscenely hard on developers to customize the generalized tools that you've given them, and this in turn makes it hard on users of that software to use the software made by developers bound by these stupid and seemingly arbitrary constraints. I find myself growing increasingly frustrated by stupid decisions of the Microsoft developers that seem obviously poor when you look at them from the perspective of a framework user. These poor decisions are costing me a lot in terms of time I have to spent developing around these decisions and researching alternatives. It's now no wonder to me how other companies can make absurd sums of money off of controls designed as work arounds for the short-sightedness of Microsoft's developers, and frankly, it's quite depressing.
- Move your software forward as often as possible, even if a few customers have to suffer lack of backward compatibility
- Move your software forward as little as possible, even if a few customers have to suffer lack of innovation
I've noticed from my own personal experiences that Apple tends to take the former attitude, whereas Microsoft tends to take the latter attitude. In the end, someone (probably you) is going to get screwed over at some point, it's just a matter of how you want it to happen. As a consumer, I generally like Apple products, so on my personal technology front, I choose the former. As a software developer, I'm forced by business constraints to accept the latter. The specific circumstances that bring me to mention this :
Today I was working with version 4.0 of the .NET framework, the very latest (and supposedly greatest) from Microsoft, along with the very latest version of their Visual Studio (2010) software. I'm also using these with WPF (the Windows Presentation Foundation) to create an application for my employer. For various reasons, I had to go back and refactor some old code, part of which required opening log files for viewing within the application. In order to open a file in WPF, you must use the OpenFileDialog class that comes with the Windows Forms library, which has been around since Windows 2000 (if not earlier, I'm admittedly not as familiar with the lifecycle of this technology as I could be). If you're a developer on the Windows platform, or even just an observant user, you'll have noticed that on the OpenFileDialog there's a Places bar on the left hand side of the dialog that provides common places for storing files, some of which are very general and will require some drilling down into subfolders to find what you actually want. I wanted to be able to add a place to this bar so that users of the application (company employees who work as technicians out on job sites) could have a direct link to the typical directory used to store log files on their machine.
It turns out that adding a folder to the places bar is obscenely difficult, and must be done through registry hacks. My question is this (directed squarely at the developers at Microsoft responsible for writing the GUI controls, specifically these dialogs) : what the hell was going through your head that would make you think that the way you've implemented these dialogs is a good thing ? It's obscenely hard on developers to customize the generalized tools that you've given them, and this in turn makes it hard on users of that software to use the software made by developers bound by these stupid and seemingly arbitrary constraints. I find myself growing increasingly frustrated by stupid decisions of the Microsoft developers that seem obviously poor when you look at them from the perspective of a framework user. These poor decisions are costing me a lot in terms of time I have to spent developing around these decisions and researching alternatives. It's now no wonder to me how other companies can make absurd sums of money off of controls designed as work arounds for the short-sightedness of Microsoft's developers, and frankly, it's quite depressing.
Tuesday, May 04, 2010
Fixing telnet disconnects
I've recently been working with a lot of embedded systems via telnet, and I'll frequently forget to disconnect my session before physically disconnecting the machine from my local LAN. Unfortunately, this results in telnet hanging the session, and me being unable to do anything else. Until now, I've just closed the window and opened another, but that became a pain in the ass, especially when I had to discard my (very useful) terminal history. Apparently, you can rescue a hung telnet session and avoid having to kill your terminal session by pressing Ctrl+5. This sends a signal to telnet to end the current session and return you to a prompt. Incredibly useful. Looks like there's a ton of other stuff where I found this useful tidbit.
Wednesday, April 21, 2010
Resolving slow SSH login times
At work, like most other organizations, we have a Linux server that we access via SSH. However, lately, I've found my use of it skyrocketing in order to test software, and log-ins and file copying have been very slow. In my efforts to find out why, I found out that sshd has in its configuration file a setting called 'UseDNS'. The default is 'yes', so even if this setting is commented out, it will try to perform reverse DNS lookups on the IP addresses of users logging in. This would be a bad thing for us, especially since we have this box locked down and unable to contact any DNS servers. After disabling DNS lookups, my login and file copy time dropped to near 0. I hope this helps somebody.
*EDIT:* I've also encountered this problem on the BeagleBone Black boards which I've recently acquired for use in a project.
*EDIT:* I've also encountered this problem on the BeagleBone Black boards which I've recently acquired for use in a project.
Tuesday, April 20, 2010
Two bash functions that will make your software development life 10% easier
... if you use SSH to move between machines a lot. Which I do. I have several machines that I work on regularly, and a centralized development machine shared with others that I frequently have to exchange files with. The following two functions are the most useful two bash functions that you can put in your .bashrc file if you use SSH as much as I do :
function ssh-create-keys {
ssh-keygen -t rsa
}
function ssh-setup-on {
cat ~/.ssh/id_rsa.pub | ssh $1 "cat - >>.ssh/authorized_keys"
}
The first sets up an SSH key file for you in your home directory, This key file will be used to identify you on other machines, and can be used for various purposes on your local machine as well. The second function logs you into the machine determined by the username@anothermachine given as the first argument to the function, and adds your public key to the list of authorized keys for that machine.
Once you've run the first function, and run the second function to setup your public key on an account on another machine, you'll now be able to use SSH to log in and copy files freely with the account on the other machine without having to enter a password. While this is incredibly convenience, it also comes with a caveat : it's dangerous. If somebody other than yourself gains physical access to your machine and can log on as you, (or use your already logged on account), they can move to those same machines freely and perform possibly malicious actions, as you. Keep that in mind.
function ssh-create-keys {
ssh-keygen -t rsa
}
function ssh-setup-on {
cat ~/.ssh/id_rsa.pub | ssh $1 "cat - >>.ssh/authorized_keys"
}
The first sets up an SSH key file for you in your home directory, This key file will be used to identify you on other machines, and can be used for various purposes on your local machine as well. The second function logs you into the machine determined by the username@anothermachine given as the first argument to the function, and adds your public key to the list of authorized keys for that machine.
Once you've run the first function, and run the second function to setup your public key on an account on another machine, you'll now be able to use SSH to log in and copy files freely with the account on the other machine without having to enter a password. While this is incredibly convenience, it also comes with a caveat : it's dangerous. If somebody other than yourself gains physical access to your machine and can log on as you, (or use your already logged on account), they can move to those same machines freely and perform possibly malicious actions, as you. Keep that in mind.
Saturday, April 17, 2010
Copying files in linux
Lately I've been doing a lot of embedded development with Linux, and copying files between systems has been a bit of a pain. Fortunately, a combination of RSync and SSH solved my problems, with a command that lets me copy files from a directory on one system to a directory on another system, recursively with symlink preservation (and even duplication !) :
rsync -azuv -e ssh user@systemaddress:~/path/to/dir/* .
Sunday, April 04, 2010
WTF ?! Windows (as of Vista) no longer supports multiple different monitors!? WHY ????
Apparently, as of Windows Vista and later, Windows no longer supports multiple video cards that don't use the same driver!! Why ? I use a Radeon and a Matrox QID to drive 6 displays on my development box, and this just entirely fucks me over. At best, I can only use four of my displays now, and that's only going to be when Matrox gets off their lazy ass and releases a Windows 7 driver for their QID LP PCIe video cards. I'm so disappointed with what's a complete step backward for Microsoft. Epic fail, Microsoft.
Saturday, January 23, 2010
Seeing where Perl looks for its modules on a system
Because we have very limited space on some embedded devices that we use that run Perl, we can only store a very few modules on these systems. This is a consequence of the fact that we run Busybox on these things, so by definition everything on these boxes is limited, if present at all. Therefore, we have to check and see if a module is available before we can use it in our code. Fortunately, there's a quick one-liner to see where Perl is looking for modules on a system :
I got this off a forum post from somewhere, and I'd post it here if I could find the link again. My apologies to the author of that forum post if they ever happen to run across this blog.
perl -e'print join "\n", @INC'
I got this off a forum post from somewhere, and I'd post it here if I could find the link again. My apologies to the author of that forum post if they ever happen to run across this blog.
Embarking on a Perl journey
A long time ago, in a job far, far away, I had to deal with some Perl. I learned just enough to get me by for the duration of the task at hand, and then pretty much forgot everything I had learned. Now, at my latest job, I'm having to deal extensively with legacy systems which have a considerable amount of logic written in Perl that needs to be either ported over to other languages (for various reasons) or updated and new things written because Perl is the only language that's both abstract enough and not too processor intensive to run on the embedded systems we deal with. Therefore, you're going to start seeing a lot more Perl posts on this blog.
Thursday, January 21, 2010
Setting up Tomcat (5.5) on Ubuntu Server 8.10
I recently ran into some old quirks when provisioning a new server for our company's web applications on Ubuntu 8.10 (Intrepid Ibex). Because the manager apps are no longer installed by default, you need to add extra packages to the list to install when installing Tomcat :
If you're copying configuration over from a previous Tomcat / Ubuntu installation, you need to make sure the permissions on all the files you copy are set correctly. In most cases, you'll have to run :
If you're securing the applications with a certificate, try to make sure it's valid for your location and ensure that you've set it properly in your server.xml configuration file. If you want useful logging, you'll also have to place a log4j.properties file in
Hope this helps.
sudo apt-get install -y tomcat5.5 tomcat5.5-admin tomcat5.5-webapps
If you're copying configuration over from a previous Tomcat / Ubuntu installation, you need to make sure the permissions on all the files you copy are set correctly. In most cases, you'll have to run :
chown -R tomcat55:adm [file and folder list here]
If you're securing the applications with a certificate, try to make sure it's valid for your location and ensure that you've set it properly in your server.xml configuration file. If you want useful logging, you'll also have to place a log4j.properties file in
$CATALINA_HOME/common/classes
Hope this helps.
Dumping just your schema with MySQL dump
A simple one-liner :
With this command, you'll be prompted for your root password. I got this from here. Simple
mysqldump -u root -p mydatabasename --no-data=true --add-drop-table=false > test_dump.sql
With this command, you'll be prompted for your root password. I got this from here. Simple
Tuesday, January 12, 2010
The Curious Case of Damned DataIntegrityViolationException
In one of the projects on which I contract, we recently started encountering a problem importing and parsing text record files into our system which previously had no problem. My first thought on hearing this was that the partner from which we obtain the files had changed the file format (again). Upon closer inspection, nothing had changed in the files. My next step was to try importing them into a development system and seeing what was going on. As it turned out, the application was catching Spring's DataIntegrityViolationException. I was floored as soon as I saw this because our application was supposed to be catching this exception behind one of the business interfaces and converting it to an internal exception which is used in business logic. After some more poking around to confirm what was really going on, I threw the problem into google, and on the second result was a post in the Spring forum made by a user having exactly the same problem I was.
To summarize their problem quickly, they were using a transaction manager, and they were intercepting their business methods (via interfaces) with Aspects, which we're also doing. The problem was this : as soon as the internal Aspects were applied to the business interface, this changed the ordering of advice applied to the interface implementor, so now the Hibernate session underneath was getting flushed later by the transaction manager, instead of in the business method where it had been flushed previously. The result of this was that now DataIntegrityViolationExceptions were being thrown outside of the interecepted method, instead of inside where it was expected. A manual session.flush() inside of a HibernateCallback within the business method fixed this :
I hope this post finds somebody else who runs into this problem.
To summarize their problem quickly, they were using a transaction manager, and they were intercepting their business methods (via interfaces) with Aspects, which we're also doing. The problem was this : as soon as the internal Aspects were applied to the business interface, this changed the ordering of advice applied to the interface implementor, so now the Hibernate session underneath was getting flushed later by the transaction manager, instead of in the business method where it had been flushed previously. The result of this was that now DataIntegrityViolationExceptions were being thrown outside of the interecepted method, instead of inside where it was expected. A manual session.flush() inside of a HibernateCallback within the business method fixed this :
/**
* @see AchPaymentNoticeOfChangeService#registerNoc(AchPaymentNoticeOfChange)
*/
@Override
public void registerNoc(final AchPaymentNoticeOfChange changeNotification) throws IllegalArgumentException, NoticeOfChangeAlreadyExistsException, Exception {
try {
getHibernateTemplate().execute(new HibernateCallback() {
@Override
public Object doInHibernate(Session session) throws HibernateException, SQLException {
session.save (changeNotification);
// flush the session to ensure that the database gets synchronized
// the end of this call, rather than waiting for any transaction
// managers to handle it and risk letting a DataIntegrityViolationException
// occur outside of this method's handling
session.flush();
return null;
}
});
} catch (DataIntegrityViolationException dive) {
throw new NoticeOfChangeAlreadyExistsException("A notice of change already exists for payment ["+changeNotification.getAchPayment().getId()+"]", dive, changeNotification);
} catch (Exception e) {
throw e;
}
}
I hope this post finds somebody else who runs into this problem.
Wednesday, December 30, 2009
Composing XML in C# 2.0 or later
When testing some custom XML serialization I was writing for a project, I ran across a quirk of the XmlWriter created by XmlWriter#Create() : The writer that gets produced by this method doesn't actually write out XML to whatever stream or file you've given it in the Create method until Close() is called on the generated writer. This means that if you've been using the XmlWriter along with a using() statement, you're fine, but if you haven't been using it, as I have in the unit tests I've been trying to write, you're going to get some unexpected results. Something to pay attention to.
Tuesday, December 22, 2009
Visual Studio (2010?) Debugging Gotchas - Part I
I recently ran across a problem where I was trying to debug some code and the Visual Studio debugger wouldn't stop in my class. It would stop in the test class from which I was debugging, but not the class I really wanted to debug. I looked on the breakpoint and noticed that it was transparent, so I hovered over it to view the tooltip, and the tooltip informed me that my class had a DebuggerStepThroughAttribute assigned to it. My first thought was "what ? that's bullshit", and then it occurred to me that I hadn't looked at the rest of the (partial) class which was defined in another file that was generated from an XML schema by xsd.exe. Sure enough, xsd.exe places DebuggerStepThroughAttribute attributes on the classes it generates. Why is this default behaviour ? This was an annoying as hell bug that took time out of my day that's better devoted to other things, like being productive so that I don't get shit from my boss. It also took more time than I'd care to admit to, largely due to my inexperience with Visual Studio and .NET. I've had yet to see anything like this coming from a Java / scripting background.
Friday, December 18, 2009
Tricky styling issues in WPF
For about the last 8 or 9 hours (spread across two days) I've been troubleshooting a styling issue in WPF. I have a list of items that I'm displaying in a ListBox that have an 'enabled' property, which is an enum with values OFF and ON. Understand that this type was generated from an XML schema which is very tightly controlled, hence why the type is not a boolean (as would make sense with a name like 'enabled'). I've been trying to create a DataTemplate for the ListBoxItems so that they'll each have an icon associated with them depending on whether they're enabled or not. My original list looked like this :
However, the images in the DataTemplate were not getting styled with the images as they should have been. As it turns out, if you're referencing default styles in Templates as above, the style resolution doesn't go outside of the template, so the default styling for all Image elements as above needs to go *inside* the DataTemplate resources :
<ListBox x:Name="ProcedureList" ItemsSource="{Binding Path=procedure}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDoubleClick" Handler="WeldProcedure_MouseDoubleClick" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="OFF">
<Setter Property="Image.Source" Value="Resources/Error_16x16_72.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="ON">
<Setter Property="Image.Source" Value="Resources/Success_16x16_72.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type model:WeldingProcedure}">
<DockPanel HorizontalAlignment="Stretch" LastChildFill="True">
<Image DockPanel.Dock="Right" Height="16" Width="16" DataContext="{Binding Path=enabled}" ToolTip="Disabled"/>
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" TextAlignment="Left" Text="{Binding Path=procedureName}"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>However, the images in the DataTemplate were not getting styled with the images as they should have been. As it turns out, if you're referencing default styles in Templates as above, the style resolution doesn't go outside of the template, so the default styling for all Image elements as above needs to go *inside* the DataTemplate resources :
<ListBox x:Name="ProcedureList" ItemsSource="{Binding Path=procedure}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDoubleClick" Handler="WeldProcedure_MouseDoubleClick" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type model:WeldingProcedure}">
<DataTemplate.Resources>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="OFF">
<Setter Property="Image.Source" Value="Resources/Error_16x16_72.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="ON">
<Setter Property="Image.Source" Value="Resources/Success_16x16_72.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<DockPanel HorizontalAlignment="Stretch" LastChildFill="True">
<Image DockPanel.Dock="Right" Height="16" Width="16" DataContext="{Binding Path=enabled}" ToolTip="Disabled"/>
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Center" TextAlignment="Left" Text="{Binding Path=procedureName}"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Friday, December 11, 2009
Generating XML objects from a schema in .NET
In my last job, I frequently made use of the JAXB library provided with the Java SDK for object de/serialization. I chose to generate my schemata from objects back then because I started off with the business objects and I knew how I wanted to serialize them to XML. Now I have the reverse situation, and I'm using a new language. As it turns out, generating classes from XML schema is dead easy : use the xsd.exe tool. It comes with .NET.
Thursday, December 10, 2009
Useful GIMP tricks - Batch file conversion
Now that I've started having to do a lot more documenting for my job, I've found myself importing a lot of pictures taken as steps in tutorials and for providing figures. Since the camera gives us a nice, high-res version that's not really suitable for documentation, I have to scale the images down so that they're in a nice, readable size. When your tutorial has 30 steps, and an image (or even two or three) is required for *each step*, this can mean a lot of manual clicking, dragging, opening and saving in an image editor. Fortunately, GIMP has a plugin called Davids Batch Processor that allows you to easily do fairly simple transforms to file and output them in the format (that's most likely) of your choice. For the simple tasks of rotating, resizing (scaling) and exporting the images in a new format, this plugin's great. There's a tutorial on the site and the plugin itself has already proven to be exceedingly useful for me. I hope it will be for you too.
Thursday, December 03, 2009
Subtleties of Perl - Reading files
I've recently begun a new job, and with it has come a whole new segment of the software development universe. The new job uses a lot of Perl, C, Bash and various other languages to get stuff done. Today, I ran afoul of a Perl idiosyncrasy that's worth making a note of, because I'm sure I'll stumble across this problem again and I'm going to need to refer to this in the future. I should also note that I'm writing this as I'm waiting for a significantly large file to parse.
We have large log files that we parse on a daily basis to extract summary information from them about mechanical systems. We read the files, and then output a summary on a secondly basis, one line at a time. Recently, I ran afoul of Perl's file reading mechanisms. When reading files in Perl, there's any number of ways to do so, and it turns out that for the longest time, we've been using the wrong one. Previously, we had been using :
We thought that this was reading the file in one line of the log file at a time, processing it, and then moving on. What it was actually doing was reading (or "slurping") the whole file into memory, and giving us an array of strings, which we processed one line at a time. After 10 minutes of cursory Googling, I ran across a tutorial which presented this :
The 'while' version of the file read actually does what we thought we where doing all along: reading one line from the file, and then doing stuff with it. The difference between the two methods is that in the 'foreach' version, the entire input file gets read into memory, whereas in the 'while' version, only a single line gets read into memory at any given time. As it turns out, another difference is that reading in a 7 MB file resulted in Perl grabbing 34 MB of memory with the 'foreach' version, but only 2.2 MB with the 'while' version. That's an ENTIRE ORDER OF MAGNITUDE in difference!. This also makes a huge difference when running Perl on memory-limited systems, as we are.
We have large log files that we parse on a daily basis to extract summary information from them about mechanical systems. We read the files, and then output a summary on a secondly basis, one line at a time. Recently, I ran afoul of Perl's file reading mechanisms. When reading files in Perl, there's any number of ways to do so, and it turns out that for the longest time, we've been using the wrong one. Previously, we had been using :
foreach my $line_of_log (<LOG>)
{
// DO STUFF WITH $line_of_log
}
We thought that this was reading the file in one line of the log file at a time, processing it, and then moving on. What it was actually doing was reading (or "slurping") the whole file into memory, and giving us an array of strings, which we processed one line at a time. After 10 minutes of cursory Googling, I ran across a tutorial which presented this :
while (<LOG>)
{
my $line_of_log = $_;
// DO STUFF WITH $line_of_log
}
The 'while' version of the file read actually does what we thought we where doing all along: reading one line from the file, and then doing stuff with it. The difference between the two methods is that in the 'foreach' version, the entire input file gets read into memory, whereas in the 'while' version, only a single line gets read into memory at any given time. As it turns out, another difference is that reading in a 7 MB file resulted in Perl grabbing 34 MB of memory with the 'foreach' version, but only 2.2 MB with the 'while' version. That's an ENTIRE ORDER OF MAGNITUDE in difference!. This also makes a huge difference when running Perl on memory-limited systems, as we are.
Tuesday, November 10, 2009
Tweaking visual studio
I've recently begun using Visual Studio 2010 Beta 2 more and more as I have increasing amounts of work that require .NET. In order to keep things nicely tabbed the both I and my boss like it, there's a setting that can be set according to instructions founds here : http://mhinze.com/tabs-whitespace-visual-studio/
Tuesday, September 29, 2009
Using the MySQL EXPLAIN statement
Recently I had been having trouble with queries on a certain table in a system that I've been maintaining. I had gone through just about every excuse for why queries on the table could be performing so slowly : the machine was slow (DB running on a VM), the webserver was slow (also running on a VM), I wasn't using the native libraries (webserver was Tomcat), I had other processes running in the background (I didn't). Then I ran across a tip on a forum suggesting usage of the MySQL EXPLAIN statement. I had known all about it for the longest time, but it never occurred to me to actually use it (I think I'm that good at writing queries, turns out : I'm wrong). After using the EXPLAIN statement, I found out that the query processor was using a suboptimal query plan which utilized an index I had added with the intent of improving performance (the index had a fairly high arity, so choosing to use it was sketchy at best in the first place). Most DBMSs should have a similar functionality built in. I think that'll be the first place I go in future.
Friday, September 18, 2009
Ubuntu 9.04 servers slow to login
Recently our production system's servers have been getting progressively slower, and it has finally gotten to the point where it's merited my full attention to remedy the situation. In my research on the problem, one of the things I came across was how incredibly slow the logins were (among other problems). After watching top during numerous logins, I saw that console-kit-daemon was going horribly slow and shooting the sshd CPU usage through the roof. After some time googling for issues related to console-kit-daemon, I found that many people were getting errors in their /var/log/daemon.log file regarding console-kit-daemon being unable to initialize policykit. After installing policykit, my logins to my production servers are now lightning fast
Tuesday, July 14, 2009
Keeping your servers up to date
As I've previously mentioned on this blog, our company uses Ubuntu for our servers. I won't re-iterate the reasons why in this post, you can search this blog using an Ubuntu tag if you're interested in them. Our servers have uptimes of months and months, and as a result, the clocks on the machines tend to drift over time, which has inspired me to start using NTP to keep the clocks synchronized. A quick Google search yielded the desired results. In order to manually synchronize with NTP, you can run the following commands :
Obviously, entering this command every day or every week gets tiresome and stupid, so you can get cron to schedule this daily for you by running the following commands :
sudo /etc/network/if-up.d/ntpdate
sudo ntpdate pool.ntp.org
Obviously, entering this command every day or every week gets tiresome and stupid, so you can get cron to schedule this daily for you by running the following commands :
echo "sudo ntpdate ntp.ubuntu.com" >> /etc/cron.daily/ntpdate
chmod 755 /etc/cron.daily/ntpdate
Tomcat keystore : too many open files - Continued
Further to my last post, it seems there was another, larger underlaying problem that was causing the exceptionally high number of connections to our servers. One of our clients "required" (I use that term loosely because they really didn't the information) extra information for transactions that was not included in the optimized change metadata that they had been instructed to query for in order to update their transactions. So instead of just querying for the update metadata, they would do that, and then they'd query for each individual transaction. Assume we page our metadata at 100 transactions / page. For example, if we were to have a batch of 800 transactions submitted by the client, instead of making 800 / 100 = 8 calls to our server to update the transactions in the batch, they'd have 8 + (800 * 1) = 808 calls to update their system. They were effectively launching a Denial of Service attack on our servers each time they wanted to update a batch of transactions. Needless to say I consulted with them on the issue and updated our change metadata to include the information they "need" (which they've already got in their system), and they've updated their system to take the number of requests down to the proper level to update their transactions. So let this be a lesson to anybody reading this blog post that has to develop systems that deal with external clients : ensure your clients fully understand the purpose and intent of all the features of our system before they start developing for it and using it.
Wednesday, July 08, 2009
Too many open sockets error in Tomcat 5.5
Our company recently started a new project. Our primary client is the first one to use it, and use they have. I've suspected they've been putting an unusually high load on our servers, and tonight it was confirmed when our server stopped handling requests, refusing them and then generating "Too many open files" errors in the Tomcat log files referring to my Tomcat SSL keystore. After doing some brief research on the error, I've discovered that this can happen when the Tomcat element in server.xml is configured with too few 'maxThreads' and a too low an 'acceptCount'. I've since tripled the number of threads and acceptable connections. Hopefully this will resolve things.
Sunday, June 28, 2009
XML namespace error with Spring WS
Today I ran into a strange error trying to access a webservice via Spring WS (Spring Web Services). The exception looked like the following :
As it turns out, the Java 6 JDK comes with its own integrated versions of Apache Xerces and Apache Xalan. The problem is that the integrated versions are old. Updating your project's POM to ensure versions of Apache Xerces >= 2.8.1 and Apache Xalan >= 2.7.0 are in your classpath will remedy the problem.
org.springframework.ws.soap.saaj.SaajSoapEnvelopeException: Could not access envelope: Unable to create envelope from given source: ; nested exception is com.sun.xml.messaging.saaj.SOAPExceptionImpl: Unable to create envelope from given source:
Caused by:
com.sun.xml.messaging.saaj.SOAPExceptionImpl: Unable to create envelope from given source:
at com.sun.xml.messaging.saaj.soap.EnvelopeFactory.createEnvelope(EnvelopeFactory.java:95)
at com.sun.xml.messaging.saaj.soap.ver1_1.SOAPPart1_1Impl.createEnvelopeFromSource(SOAPPart1_1Impl.java:51)
at com.sun.xml.messaging.saaj.soap.SOAPPartImpl.getEnvelope(SOAPPartImpl.java:106)
at org.springframework.ws.soap.saaj.Saaj13Implementation.getEnvelope(Saaj13Implementation.java:145)
at org.springframework.ws.soap.saaj.SaajSoapMessage.getEnvelope(SaajSoapMessage.java:84)
at org.springframework.ws.soap.AbstractSoapMessage.getSoapHeader(AbstractSoapMessage.java:42)
at org.springframework.ws.soap.server.SoapMessageDispatcher.handleRequest(SoapMessageDispatcher.java:91)
at org.springframework.ws.server.MessageDispatcher.dispatch(MessageDispatcher.java:189)
at org.springframework.ws.server.MessageDispatcher.receive(MessageDispatcher.java:166)
at org.springframework.ws.transport.support.WebServiceMessageReceiverObjectSupport.handle(WebServiceMessageReceiverObjectSupport.java:78)
at org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter.handle(WebServiceMessageReceiverHandlerAdapter.java:60)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:819)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:754)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:399)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:364)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:709)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:178)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:126)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:107)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869)
at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664)
at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527)
at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:80)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
at java.lang.Thread.run(Unknown Source)
Caused by: javax.xml.transform.TransformerException: org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)
at com.sun.xml.messaging.saaj.util.transform.EfficientStreamingTransformer.transform(EfficientStreamingTransformer.java:371)
at com.sun.xml.messaging.saaj.soap.EnvelopeFactory.createEnvelope(EnvelopeFactory.java:83)
... 30 more
Caused by: org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.
at com.sun.org.apache.xerces.internal.dom.AttrNSImpl.setName(Unknown Source)
at com.sun.org.apache.xerces.internal.dom.AttrNSImpl.(Unknown Source)
at com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl.createAttributeNS(Unknown Source)
at com.sun.xml.messaging.saaj.soap.SOAPDocumentImpl.createAttributeNS(SOAPDocumentImpl.java:142)
at com.sun.org.apache.xerces.internal.dom.ElementImpl.setAttributeNS(Unknown Source)
at com.sun.xml.messaging.saaj.soap.impl.ElementImpl.setAttributeNS(ElementImpl.java:1190)
at com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM.startElement(Unknown Source)
at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.closeStartTag(Unknown Source)
at com.sun.org.apache.xml.internal.serializer.ToSAXHandler.flushPending(Unknown Source)
at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.startElement(Unknown Source)
at org.xml.sax.helpers.XMLFilterImpl.startElement(Unknown Source)
at com.sun.xml.messaging.saaj.util.RejectDoctypeSaxFilter.startElement(RejectDoctypeSaxFilter.java:157)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(Unknown Source)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source)
at org.xml.sax.helpers.XMLFilterImpl.parse(Unknown Source)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(Unknown Source)
... 34 more
As it turns out, the Java 6 JDK comes with its own integrated versions of Apache Xerces and Apache Xalan. The problem is that the integrated versions are old. Updating your project's POM to ensure versions of Apache Xerces >= 2.8.1 and Apache Xalan >= 2.7.0 are in your classpath will remedy the problem.
Wednesday, May 20, 2009
Some helpful troubleshooting tips for SFTP jails
Further to my previous post on SFTP jails, I've run across a few issues. When logging in via command line after first setting up the jail, you may encounter immediate disconnects with the response "Connection Closed". One possible cause of this is missing files, specifically "/lib64/ld-linux-x86-64.so.2" under the jail. There are other files that can possibly be missing, but that's a biggie if you're running a 64-bit kernel. If you're running WinSCP, you may also encounter the error message "Is the host running a SFTP server?"
Monday, May 11, 2009
A quick note on intercepting annotations with Spring AOP
Spring AOP is incredibly powerful. One of the most powerful features of the framework is that it lets you intercept methods that are decorated with particular annotations using the following AOP expression :
This can be used in place of :
...or in combination with it. But one important point : the annotation being intercepted must be in service method implementations (ie concrete class implementations), not on the service method interface declarations! Otherwise Spring AOP will not be able to read and intercept the annotated methods / classes
@annotation(my.package.MyAnnotation)
This can be used in place of :
execution(public * my.package.MyService.serviceMethod(*))
...or in combination with it. But one important point : the annotation being intercepted must be in service method implementations (ie concrete class implementations), not on the service method interface declarations! Otherwise Spring AOP will not be able to read and intercept the annotated methods / classes
Friday, April 17, 2009
Setting up Microsoft Outlook 2007 with Microsoft Exchange
...via RPC over HTTPS :
1. Ensure you have your root certificate in your trusted authority store in the Microsoft Management Console (mmc.exe)
2. Ensure you have your personal certificate in your personal key store in the Microsoft Management Console (mmc.exe)
3. Ensure you've got the correct server settings
1. Ensure you have your root certificate in your trusted authority store in the Microsoft Management Console (mmc.exe)
2. Ensure you have your personal certificate in your personal key store in the Microsoft Management Console (mmc.exe)
3. Ensure you've got the correct server settings
Monday, April 13, 2009
Setting up a VPN connection to an Astaro security applicance on Mac OS X
So I finally got a working VPN configuration to be able to connect my shiny new MacBook Pro to our VPN on our production system which uses an Astaro firewall appliance. Turns out it's fairly easy too :
Oh, and make sure you've actually got Tunnelblick started, or else you won't be able to connect ;)
- Go to your Astaro firewall's user profile page, ie login as a user, and download the key / OpenVPN configuration package for your router. The great thing about these appliances is that they provide the package for you right from the device
- Download Tunnelblick. It's an (apparently very easy to use) GUI front end for OpenVPN on Mac OS X. The latest version (as of the time of this writing) is 3.0 beta 10. Tunnelblick is hosted on Google Code. The installation's even easier. Load up the disk image, and drag the sole application icon into your applications folder.
- Open the VPN package you downloaded from your Astaro firewall appliance, and copy everything in it to the ~/Library/openvpn folder created by the Tunnelblick installation
- Tunnelblick should automatically detect the additional files and provide a listing for the Astaro configuration in its connection list. Once you've got the item in the list, just click on it and it should automagically connect to the Astaro firewall appliance and your VPN connection should be working.
Oh, and make sure you've actually got Tunnelblick started, or else you won't be able to connect ;)
Tuesday, April 07, 2009
Playing with my new Mac
I recently managed to convince my employer to provide me with a MacBook Pro so that I could start developing in-house iPhone applications for the company. I'm not yet at the point where I'm writing applications, but I am installing tons of stuff on the laptop so that I can use it as my mobile computer for when I'm on the road for work. It certainly has its quirks that I have to get used to over running everything in Windows. This will be a very brief post, but suffice it to say that I've learned a few things :
1) When it comes to Maven, ensure that JAVA_HOME is set in your environment variables.
2) Make sure you're choosing the right JVM. MacOSX comes with a bunch of default JVMs installed, and you can configure them through the Java control panel in Applications -> Utilities
1) When it comes to Maven, ensure that JAVA_HOME is set in your environment variables.
2) Make sure you're choosing the right JVM. MacOSX comes with a bunch of default JVMs installed, and you can configure them through the Java control panel in Applications -> Utilities
Wednesday, March 25, 2009
More Hibernate glitches, part 2
I had previously written on an Expected positional parameter glitch in Hibernate when joining between entities using OneToOne and PrimaryKey + ForeignKey columns. It turns out, Hibernate has this problem no matter what field you query on the joined entity if you're quering by another entity. A way around this is to use the ID property of the joined entities and add another association to your HQL / Criteria query.
ie So if you have Person and Address having a OneToOne relationship
... and you want to query for a Person by Address, you would probably write something like :
... in HQL. Well, this is where you start running into the Expected positional parameter glitch. Instead, write your query like this :
... and pass in a long / int / whatever type you use for an identifier for Person / Address instead and this will work around the glitch until it can be fixed.
ie So if you have Person and Address having a OneToOne relationship
public class Person {
@OneToOne
private Address address;
}
... and you want to query for a Person by Address, you would probably write something like :
from Person p where p.address = ?
... in HQL. Well, this is where you start running into the Expected positional parameter glitch. Instead, write your query like this :
from Person p where p.address.id = ?
... and pass in a long / int / whatever type you use for an identifier for Person / Address instead and this will work around the glitch until it can be fixed.
Sunday, March 15, 2009
Stupid MySQL tricks
Recently, I found that our secondary server's MySQL partition was starting to get full. After running du -h --max-depth=1 several times recursively, I found that the source was the MySQL binary logs. As it turns out, MySQL comes with a very handy command for purging all log files. If you run :
show master logs, you'll be given a list of the logs that the MySQL server is currently using. You can clear out old log files (some of which can be very large) by running the command
purge master logs to 'log-name-here -from-display'. This will remove the old log files from the hard disk and save up some potentially much needed space.
Friday, March 13, 2009
Spring Webflow 2 Quirks
As I recently found out after a marathon debugging session, if you have a <transition> element that executes actions, and any beans involved in an expression evaluation don't exist in your application context, SWF2 will spin it's wheels instead of properly resolving things, leaving your flow in the lurch. At the time of this writing, I'm using Spring Webflow 2.
Thursday, March 12, 2009
More Hibernate glitches
I've recently discovered the joy of actually getting OneToOne primary key columns on objects working, so I've started using them more often (where appropriate). Naturally, increased use means increased chances of finding a bug, which I have. It seems there's a parser problem in HQL when it comes to querying on ID columns that are also objects. If you try to query on the object type (ie Person) rather than the ID type (ie Long), you'll get an exception saying something along the lines of :
The above line is from HHH-2254, but it's representative of the problem I ran across. The problem is that hibernate doesn't properly parse and fill in the parameters in the query. You can get around this by changing the query to use a simple type for ID instead of the object type.
Expected positional parameter count: 1, actual parameters: [Parent@bec357b] [from Child this where this.id.parent = ?]
The above line is from HHH-2254, but it's representative of the problem I ran across. The problem is that hibernate doesn't properly parse and fill in the parameters in the query. You can get around this by changing the query to use a simple type for ID instead of the object type.
Wednesday, March 11, 2009
Stupid Hibernate Tricks
I've recently been finding myself looking for a way to get Hibernate to override the default type for strings (varchar) and use char instead in order to boost performance of the database for fields that I know are going to have a fixed length. During my search, I came upon a couple of solutions.
- Using annotations
- You can use the 'columnDefinition' attribute of the @Column annotation to specify the SQL type directly. This has the unfortunate side effective of possibly reducing the database portability of your application.
- Using SQL Dialects
- You can override one of Hibernate's built in SQL Dialects and make char() types the default for strings. One other interesting possibility this opens up is that it allows you to only override the definition for certain lengths, ie, you could make any strings less than or equal to 4 characters in length use a char() definition, and the rest could use varchar definitions. This is done using the 'registerColumnType' method of Dialect.
Wednesday, February 25, 2009
I love Dojo
I have recently discovered the magic that is Dojo. I love it. The widgets that it provides are uber-useful and have really boosted the functionality of our site. And once I integrated it with Spring Webflow 2, using some custom tags I wrote, my development speed shot right up.
Dojo Home Page
Dojo Campus - Great tutorial site
Dojo Home Page
Dojo Campus - Great tutorial site
Wednesday, February 04, 2009
Solved! Finally fixed classpath issues with Maven, Eclipse and JUnit
I have several pieces of code in one of our company's projects that dynamically reads the classpath and scans for classes that have certain annotations and values in those annotations. For the longest time, I was able to unit test these successfully. Then one day, I started getting strange ClassNotFoundExceptions when trying to execute the tests, and I could never figure out why. The project still ran just fine on our servers, so I let the issue slide at the time because I was pressed for time and needed to get other shit done. Overtime, I continued to let the problem bounce up and down over rocks as I tried to unit test certain things and couldn't because of this problem. Today I finally got fed up and wound up investing 3 hours troubleshooting the problem, and here's what happened:
Right around the same time I started getting this problem, I had upgraded my version of the M2Eclipse plugin (which I use to integrate the Maven build system and Eclipse) to a new version. As it turns out, in the intervening versions, the Maven developers changed how Maven integrates with the Eclipse classpath, and if you got the plugin to update your project's Eclipse configuration, it would specify separated output folders for your projects code and your project's test classes, and that's what caused the problem. Separating the two meant I could no longer dynamically resolve my project's model classes in a test environment, because the classpath visitor wouldn't receive the correct source folder from the test environment's classloader. So, the solution as it turned out, was to have Eclipse's output go into the same folder for source and test classes.
Right around the same time I started getting this problem, I had upgraded my version of the M2Eclipse plugin (which I use to integrate the Maven build system and Eclipse) to a new version. As it turns out, in the intervening versions, the Maven developers changed how Maven integrates with the Eclipse classpath, and if you got the plugin to update your project's Eclipse configuration, it would specify separated output folders for your projects code and your project's test classes, and that's what caused the problem. Separating the two meant I could no longer dynamically resolve my project's model classes in a test environment, because the classpath visitor wouldn't receive the correct source folder from the test environment's classloader. So, the solution as it turned out, was to have Eclipse's output go into the same folder for source and test classes.
Tuesday, January 06, 2009
Allowing external root access to your MySQL database
...is recommended against in the MySQL documentation, but it can be oh so handy for running scripts and adding databases, tables and views when you can't (or don't want to) log into that remote system on the command line to update it. However, when you try it (at least on a fresh Ubuntu install), you may be greeted with an Error 2003. This happens when your system does not allow root external access. To enable external access, you can run this query (as root) on your server from the command line (must be localhost) :
* EDIT * : The above statement does not necessarily copy all privileges over to the new entry for 'root'@'%' in the mysql.user table. A better way to accomplish full external root access is the following :
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY ('myrootpassword');* EDIT * : The above statement does not necessarily copy all privileges over to the new entry for 'root'@'%' in the mysql.user table. A better way to accomplish full external root access is the following :
DELETE FROM `mysql`.`user` WHERE User = 'root' AND Host = '%';
INSERT INTO `mysql`.`user` (Host, User, Password, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, Execute_priv, Repl_slave_priv, Repl_client_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Create_user_priv, ssl_type, ssl_cipher, x509_issuer, x509_subject, max_questions, max_updates, max_connections, max_user_connections) (SELECT '%', User, Password, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, Execute_priv, Repl_slave_priv, Repl_client_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Create_user_priv, ssl_type, ssl_cipher, x509_issuer, x509_subject, max_questions, max_updates, max_connections, max_user_connections FROM `mysql`.`user` WHERE User = 'root' AND Host = 'localhost');
FLUSH PRIVILEGES;
Labels:
2003,
error 2003,
errors,
external login,
mysql,
root,
ubuntu
Setting up MySQL server on Ubuntu
...is really easy. Run the command :
Once that's done, you'll need to perform some additional configuration and setup the root password. My added configuration involves making table name comparisons all lower case (convenient for queries) and making the server run entirely in utf8. In order to configure the server as such, you'll need to make the following changes :
Add the following lines to the [mysqld] section of the my.cnf configuration :
Add the following line to the [mysql] section of my.cnf (note that this is not the same as the [mysqld] section, the difference being the 'd' on the end) :
In order to have the Ubuntu MySQL installation properly run on external hosts, you'll have to comment out the line that looks like :
After you've configured the my.cnf file, you'll have to stop the server, and restart it with --skip-grant-tables enabled so that you can have free, unfettered access to the system with privileges disabled. This is required so that you can set the initial root password. Run the following commands as root :
The latter command will start the MySQL server with privileges disabled.
Then log in with the command
Once logged in, run the command :
This will set the user password to 'thepassword' (though I recommend you don't use that exact password). Once you've set the root password, exit mysql and run the command
This will restart the server as usual, so that you'll no longer have unlimited privileges (and neither will anyone else).
apt-get install -y mysql-server
Once that's done, you'll need to perform some additional configuration and setup the root password. My added configuration involves making table name comparisons all lower case (convenient for queries) and making the server run entirely in utf8. In order to configure the server as such, you'll need to make the following changes :
Add the following lines to the [mysqld] section of the my.cnf configuration :
default-character-set=utf8
default-collation=utf8_general_ci
lower_case_table_names=1
Add the following line to the [mysql] section of my.cnf (note that this is not the same as the [mysqld] section, the difference being the 'd' on the end) :
default-character-set=utf8
In order to have the Ubuntu MySQL installation properly run on external hosts, you'll have to comment out the line that looks like :
bind-address 127.0.0.1
After you've configured the my.cnf file, you'll have to stop the server, and restart it with --skip-grant-tables enabled so that you can have free, unfettered access to the system with privileges disabled. This is required so that you can set the initial root password. Run the following commands as root :
/etc/init.d/mysql stop
mysqld_safe --skip-grant-tables --skip-networking &
The latter command will start the MySQL server with privileges disabled.
Then log in with the command
mysql -u root -h localhost
Once logged in, run the command :
UPDATE `mysql`.`user` SET Password = PASSWORD('thepassword') WHERE User = 'root';
This will set the user password to 'thepassword' (though I recommend you don't use that exact password). Once you've set the root password, exit mysql and run the command
/etc/init.d/mysql restart
This will restart the server as usual, so that you'll no longer have unlimited privileges (and neither will anyone else).
Setting up an SSH Server on Ubuntu
Ubuntu is usually pretty good about coming pre-configured with everything you need for your system, but one thing I've found that it's lacking (at least the server distribution is, anyway) is a pre-setup SSH server. Fortunately though, it's quite easy to set one up. All you need to do is issue the command:
...as root. If you get an error to the effect of :
...then you'll have to run :
...to update your system's packages first.
apt-get install openssh-server
...as root. If you get an error to the effect of :
Package does not exist
...then you'll have to run :
apt-get update
...to update your system's packages first.
Tuesday, December 30, 2008
Configuring MySQL for table case-insensitivity
While trying to add a recent update to our systems, I noticed that our demo system wouldn't start, which was very strange considering I had no problems on my local test system. I eventually traced the problem down to MySQL not being able to find certain tables. This was because I had created the tables in a different case than the Quartz library was expecting. My local testing box is a Windows box whereas our demo and production systems run Linux, the difference being that my Windows MySQL installation has case-insensitivity on by default, whereas the Linux installations do not. In order to remedy this, I had to explicitly add a line to my my.ini / my.cnf configurations :
You can determine the case-sensitivity of your own MySQL installation by logging in with the command line client or running the query browser and entering the following command :
lower_case_table_names=1
You can determine the case-sensitivity of your own MySQL installation by logging in with the command line client or running the query browser and entering the following command :
SHOW VARIABLES LIKE 'lower_case%'
Thursday, December 11, 2008
Integrating Quartz and Spring using persistent jobs
I've recently found myself having a lot of jobs running in the background of our Java applications, often requiring changes to the scheduling based on my boss' whims. Combine this with the fact that our system's API is now seeing heavy use, and having the triggers configured in the Spring application context (and thus requiring a restart in order to change them) just doesn't cut it any more. Thus, I now find myself needing persistent jobs. However, there's not a lot of documentation out there for dealing with Quartz' persistent job feature in Spring, so I'm going to hopefully provide some help. To start, you need to create the quartz tables in whatever database you're going to be using. There are scripts for creating these tables in the Quartz distribution available on their website for whatever database you may be using. For convenience, I've copied the MySQL Inno DB script here :
Once this is done, you'll need to integrate Quartz with Spring. If you're using Maven 2, getting Quartz into your project is as simple as adding the following snippet to the <dependencies> section of your pom.xml configuration file.
Note that you're going to need Quartz version 1.6.0 to work with Spring 2.5.3 or greater, otherwise you can use Quartz 1.5.x. After you've got Quartz into your project, you'll need to define a Scheduler that accesses your database to schedule your jobs. Fortunately, Spring happens to come with a handy FactoryBean for defining Quartz schedulers. I've used the following :
The above Spring beans configuration snippet requires some explanation, so let's go through it.
At this point in time, you've got the tables which Quartz requires for persistent jobs defined (and presumably accessible) in your database, you've got the Quartz libraries present in your classpath, and you've got Quartz configured within Spring so that Quartz will run and check the database for jobs to be run. However, we're still missing a few more important pieces of information.
For the rest of this article, lets assume that you've set your Quartz table prefix as 'qrtz_'. In your database, lets look at the definition of the 'qrtz_job_details' table. It includes columns for defining job_name, job_group, description, job_class_name, is_durable, is_volatile, is_stateful, requests_recovery, and job_data.
The 'job_name' field is, as you've almost certainly guessed, the name of the job that you want to define. The job must also have a 'job_group' specified because triggers can be used to signal the execution of entire groups of jobs at once, not just single jobs. The two of these fields together form a unique key for the table. If you really don't give a shit about grouping jobs, you can just use Quartz' default group name, which is 'DEFAULT'. You also need to set a 'job_class_name'. This value is a fully qualified Java class name that must have a default, no-arg constructor (ie it's a bean, according to the Java Beans specification). This class will get instantiated at run time by the SpringBeanJobFactory and run as a Quartz job. Note that this class, obviously, must implement the org.quartz.Job interface. A decription of the job can be put in the 'description' field if you want to have a log-friendly message available to you. The is_durable, is_volatile, is_stateful, and requests_recovery fields are all (essentially) boolean fields that Quartz defines as being single characters. These fields can have the values 'Y' or 'N'. See the Quartz API for org.quartz.JobDetail for further elaboration on the meaning of these fields. The 'job_data' field is a BLOB object that's used to serialize the 'jobDataMap' (java.util.Map) associated with the JobDetail. In order to populate this field, you'll obviously have to write some JDBC code.
The 'qrtz_job_details' table is, obviously, where you store the information for the org.quartz.Jobs you want to run. Anytime you need to define a new job, you'll need to insert a new row into this table, properly configured of course and paying particular attention to the 'job_class_name' field.
Ok, so assuming you've got a couple rows in this table (ie defined a couple of jobs), now you'll need to schedule them so that, at some point, they'll actually run. This is where triggers come in. Now, in Quartz, there are a few different methods of triggering off Jobs, by far the most common being org.quartz.SimpleTriggers (qrtz_simple_triggers table) and org.quartz.CronTriggers (qrtz_cron_triggers table). SimpleTriggers have their use, but our company uses some pretty complex scheduling at times, so I'm going to do an example of using the cron triggers.
The 'qrtz_cron_triggers' table is pretty simple, it only has four fields : 'trigger_name', 'trigger_group', 'cron_expression' and 'time_zone_id', all of which should be pretty indicative of their content. The 'time_zone_id' field should be a valid id for a java.util.TimeZone, ie 'America/Denver'. See the documentation for that class for more on valid IDs.
Once you've set up a trigger with a valid name, group (ie DEFAULT), cron expression and TimeZone ID, you're pretty much ready to go. When Quartz starts up with your application, it'll consult the 'qrtz_triggers' table and ensure that entries exist for all of your corresponding cron / simple / blob triggers. It'll then proceed to fire off triggers and the Scheduler will pass off jobs to the SpringBeanJobFactory for execution. Now, if you haven't figured it out already, if you've got a lot of jobs, so far from what this article has shown you, you'll have to create a Quartz Job implementation for each job you want to run off, and even then, it'll have to be pretty simple, since you'll have to be able to instantiate it with a no-arg constructor, and so far you've got no way of injecting it with anything from Spring, aside from simple properties that are defined by the 'schedulerContextAsMap' property on the Spring SchedulerFactoryBean. So, what I did was create a class that uses its JobDetail#name as a convention for getting a bean from the application context, assumes it's an org.quartz.Job implementation, and executes it. The code is attached :
Now, this makes the persisted jobs a lot more capable : we can now run org.quartz.Job beans that are declared in our Spring ApplicationContext. What if we want to be able to run arbitrary methods on arbitrary beans as jobs ? Well, we can write an adapter class for that too, quite similar (though not as functional as) Spring's MethodInvokingJobDetailFactoryBean :
Note that the class above is implements org.quartz.Job, not org.quartz.JobDetail as is the product of MethodInvokingJobDetailFactoryBean. I hope this article has been useful for anybody reading it, feel free to comment on it.
DROP TABLE IF EXISTS QRTZ_JOB_LISTENERS;
DROP TABLE IF EXISTS QRTZ_TRIGGER_LISTENERS;
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS(
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
IS_STATEFUL VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_JOB_LISTENERS (
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
JOB_LISTENER VARCHAR(200) NOT NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP,JOB_LISTENER),
INDEX (JOB_NAME, JOB_GROUP),
FOREIGN KEY (JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_TRIGGERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
INDEX (JOB_NAME, JOB_GROUP),
FOREIGN KEY (JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(7) NOT NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
INDEX (TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_CRON_TRIGGERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
INDEX (TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_BLOB_TRIGGERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
INDEX (TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_TRIGGER_LISTENERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
TRIGGER_LISTENER VARCHAR(200) NOT NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_LISTENER),
INDEX (TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_CALENDARS (
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (CALENDAR_NAME))
TYPE=InnoDB;
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_FIRED_TRIGGERS (
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_STATEFUL VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (ENTRY_ID))
TYPE=InnoDB;
CREATE TABLE QRTZ_SCHEDULER_STATE (
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (INSTANCE_NAME))
TYPE=InnoDB;
CREATE TABLE QRTZ_LOCKS (
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (LOCK_NAME))
TYPE=InnoDB;
INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS');
INSERT INTO QRTZ_LOCKS values('JOB_ACCESS');
INSERT INTO QRTZ_LOCKS values('CALENDAR_ACCESS');
INSERT INTO QRTZ_LOCKS values('STATE_ACCESS');
INSERT INTO QRTZ_LOCKS values('MISFIRE_ACCESS');
commit;
Once this is done, you'll need to integrate Quartz with Spring. If you're using Maven 2, getting Quartz into your project is as simple as adding the following snippet to the <dependencies> section of your pom.xml configuration file.
<dependency>
<groupId>opensymphony</groupId>
<artifactId>quartz</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
Note that you're going to need Quartz version 1.6.0 to work with Spring 2.5.3 or greater, otherwise you can use Quartz 1.5.x. After you've got Quartz into your project, you'll need to define a Scheduler that accesses your database to schedule your jobs. Fortunately, Spring happens to come with a handy FactoryBean for defining Quartz schedulers. I've used the following :
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobFactory">
<bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory"/>
</property>
<property name="dataSource" ref="mainDataSource" />
<property name="transactionManager" ref="mainDataSourceTransactionManager" />
<property name="quartzProperties">
<util:properties location="/WEB-INF/config/quartz.properties"/>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="waitForJobsToCompleteOnShutdown" value="true" />
</bean>
The above Spring beans configuration snippet requires some explanation, so let's go through it.
- The property 'jobFactory' is a Spring implementation of the Quartz JobFactory interface. When the scheduler encounters a trigger in the database that's to be fired, it loads the corresponding JobDetails from the database, loads it into a JobDetail object, places that JobDetail object into a TriggerFiredBundle, and passes the TriggerFiredBundle to the JobFactory interface to obtain a Job that's to be run. Now, you might think that because of the name of Spring's JobFactory implementation, it's going to do something with a bean defined in the Spring ApplicationContext, right ? Wrong. What this implementation does is get the Job class provided by JobDetail.getClass(), instantiate a new instance of it with a default no-arg constructor, possibly populate it with properties, and then run the Job. Which sucks, and is kind of useless, because no Jobs I need can do so without getting something from Spring's ApplicationContext. More on this later.
- The 'dataSource' property should be the javax.sql.DataSource used by your application to connect to whatever database you're using that stores the persistent Jobs in the tables used by Quartz.
- The 'transactionManager' property should be set to a org.springframework.transaction.PlatformTransactionManager implementation that's used to demarcate transactions in the database. If you don't have one for your dataSource already defined, then it's fortunate that Spring comes with one that you can define and use, like so :
<bean id="mainDataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="mainDataSource" /> - The 'quartzProperties' property should be configured with a java.util.Properties instance containing configuration values for Quartz. A sample is posted here for you :
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5
org.quartz.threadPool.threadPriority=4
org.quartz.jobStore.tablePrefix=qrtz_
org.quartz.jobStore.isClustered=false
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
This sample contains a few simple properties that should be enough to get you started. There are numerous others that you should look into in the Quartz documentation. The first three properties are for configuring Quartz' thread-pooling capabilities. The latter three are for configuring the database (and access thereto-) that Quartz uses for storing persistent jobs. The table prefix is used to prefix Quartz' tables so that they don't conflict with any pre-existing tables in your database. The 'isClustered' property is used to determine whether Quartz is acting in a cluster. Clustered Quartz configurations are beyond the scope of this article. The 'driverDelegateClass' property is used to determine the class Quartz will use for dealing with your specific dialect of SQL (ie MySQL, MS-SQL, PostgreSQL, etc). - The 'applicationContextSchedulerContextKey' property sets a key that's used to access the Spring ApplicationContext in the JobExecutionContext given to a Job implementation at run time after the SpringBeanJobFactory has populated it for you. This is important as this property does not have a default value and will not put the ApplicationContext into the JobExecutionContext for you unless you specify one.
- The 'waitForJobsToCompleteOnShutdown' property specifies, well, I think you can figure that one out.
At this point in time, you've got the tables which Quartz requires for persistent jobs defined (and presumably accessible) in your database, you've got the Quartz libraries present in your classpath, and you've got Quartz configured within Spring so that Quartz will run and check the database for jobs to be run. However, we're still missing a few more important pieces of information.
- JobDetails stored in the database describing jobs to be run
- Triggers defined in the database for describing when JobDetails will be executed
For the rest of this article, lets assume that you've set your Quartz table prefix as 'qrtz_'. In your database, lets look at the definition of the 'qrtz_job_details' table. It includes columns for defining job_name, job_group, description, job_class_name, is_durable, is_volatile, is_stateful, requests_recovery, and job_data.
The 'job_name' field is, as you've almost certainly guessed, the name of the job that you want to define. The job must also have a 'job_group' specified because triggers can be used to signal the execution of entire groups of jobs at once, not just single jobs. The two of these fields together form a unique key for the table. If you really don't give a shit about grouping jobs, you can just use Quartz' default group name, which is 'DEFAULT'. You also need to set a 'job_class_name'. This value is a fully qualified Java class name that must have a default, no-arg constructor (ie it's a bean, according to the Java Beans specification). This class will get instantiated at run time by the SpringBeanJobFactory and run as a Quartz job. Note that this class, obviously, must implement the org.quartz.Job interface. A decription of the job can be put in the 'description' field if you want to have a log-friendly message available to you. The is_durable, is_volatile, is_stateful, and requests_recovery fields are all (essentially) boolean fields that Quartz defines as being single characters. These fields can have the values 'Y' or 'N'. See the Quartz API for org.quartz.JobDetail for further elaboration on the meaning of these fields. The 'job_data' field is a BLOB object that's used to serialize the 'jobDataMap' (java.util.Map) associated with the JobDetail. In order to populate this field, you'll obviously have to write some JDBC code.
The 'qrtz_job_details' table is, obviously, where you store the information for the org.quartz.Jobs you want to run. Anytime you need to define a new job, you'll need to insert a new row into this table, properly configured of course and paying particular attention to the 'job_class_name' field.
Ok, so assuming you've got a couple rows in this table (ie defined a couple of jobs), now you'll need to schedule them so that, at some point, they'll actually run. This is where triggers come in. Now, in Quartz, there are a few different methods of triggering off Jobs, by far the most common being org.quartz.SimpleTriggers (qrtz_simple_triggers table) and org.quartz.CronTriggers (qrtz_cron_triggers table). SimpleTriggers have their use, but our company uses some pretty complex scheduling at times, so I'm going to do an example of using the cron triggers.
The 'qrtz_cron_triggers' table is pretty simple, it only has four fields : 'trigger_name', 'trigger_group', 'cron_expression' and 'time_zone_id', all of which should be pretty indicative of their content. The 'time_zone_id' field should be a valid id for a java.util.TimeZone, ie 'America/Denver'. See the documentation for that class for more on valid IDs.
Once you've set up a trigger with a valid name, group (ie DEFAULT), cron expression and TimeZone ID, you're pretty much ready to go. When Quartz starts up with your application, it'll consult the 'qrtz_triggers' table and ensure that entries exist for all of your corresponding cron / simple / blob triggers. It'll then proceed to fire off triggers and the Scheduler will pass off jobs to the SpringBeanJobFactory for execution. Now, if you haven't figured it out already, if you've got a lot of jobs, so far from what this article has shown you, you'll have to create a Quartz Job implementation for each job you want to run off, and even then, it'll have to be pretty simple, since you'll have to be able to instantiate it with a no-arg constructor, and so far you've got no way of injecting it with anything from Spring, aside from simple properties that are defined by the 'schedulerContextAsMap' property on the Spring SchedulerFactoryBean. So, what I did was create a class that uses its JobDetail#name as a convention for getting a bean from the application context, assumes it's an org.quartz.Job implementation, and executes it. The code is attached :
public class SpringBeanDelegatingJob implements Job {
private static final Log LOGGER = LogFactory.getLog(SpringBeanDelegatingJob.class);
public static final String APPLICATION_CONTEXT_KEY = "applicationContext";
@SuppressWarnings("unchecked")
public void execute(JobExecutionContext arg0) throws JobExecutionException {
JobDetail jobDetail = arg0.getJobDetail();
String beanName = substringBefore(jobDetail.getName(), "Detail");
if (LOGGER.isInfoEnabled()) {
LOGGER.info ("Running SpringBeanDelegatingJob - Job Name ["+jobDetail.getName()+"], Group Name ["+jobDetail.getGroup()+"]");
LOGGER.info ("Delegating to bean ["+beanName+"]");
}
ApplicationContext applicationContext = null;
try {
applicationContext = (ApplicationContext) arg0.getScheduler().getContext().get(APPLICATION_CONTEXT_KEY);
} catch (SchedulerException e2) {
throw new JobExecutionException("Holy fuck, there was some kind of god-damned problem with the fucking Scheduler", e2);
}
Job bean = null;
try {
bean = (Job) applicationContext.getBean (beanName, Job.class);
} catch (BeansException e1) {
throw new JobExecutionException("Unable to retrieve target bean that is to be used as a job source", e1);
}
bean.execute (arg0);
return;
}
}
Now, this makes the persisted jobs a lot more capable : we can now run org.quartz.Job beans that are declared in our Spring ApplicationContext. What if we want to be able to run arbitrary methods on arbitrary beans as jobs ? Well, we can write an adapter class for that too, quite similar (though not as functional as) Spring's MethodInvokingJobDetailFactoryBean :
public class SpringBeanMethodInvokingJob implements InitializingBean, Job {
private Object targetBean;
private String targetMethod;
//Constructors
public SpringBeanMethodInvokingJob() {
super();
}
//Behaviour Methods
public void execute(JobExecutionContext arg0) throws JobExecutionException {
Method method = null;
try {
method = targetBean.getClass().getMethod(targetMethod);
} catch (Exception e) {
throw new JobExecutionException("Unable to get targetMethod ["+targetMethod+
"] on bean with class ["+targetBean.getClass().getName()+"]");
}
try {
method.invoke(targetBean);
} catch (Exception e) {
throw new JobExecutionException("Unable to invoke method ["+method.getName()+"] on bean ["+targetBean.toString()+"]");
}
return; //done
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(targetBean, "'targetBean' cannot be null");
Assert.isTrue(isNotBlank(targetMethod), "'targetMethod' cannot be blank");
}
//Property Accessors
@Required
public final void setTargetBean(Object targetBean) {
this.targetBean = targetBean;
}
@Required
public final void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}
Note that the class above is implements org.quartz.Job, not org.quartz.JobDetail as is the product of MethodInvokingJobDetailFactoryBean. I hope this article has been useful for anybody reading it, feel free to comment on it.
Friday, December 05, 2008
The greatest MySQL companion ever
I just recently found out about the 'show processlist' command in MySQL that lets you get a resultset of all the processes MySQL is currently using and what query they're running. Today I was working in top monitoring the performance of our servers as I restarted our production server. MySQL popped up briefly on the top list of running processes and it occurred to me, "Wouldn't it be great of there was something like this for MySQL?". So while I was waiting, I popped open google, and ran a query for a MySQL and top. To my surprise, up came mytop. It's literally top for MySQL and it's so ridiculously useful you wouldn't believe it. Even better, it's got a .deb made for it and it's in the Ubuntu repositories, so installing it is as simple as running :
It's a good day today :)
apt-get install mytop
It's a good day today :)
Setting a user's password in MySQL
I've recently found the need to change a bunch of passwords in MySQL. The command to do it is as follows :
You can also read the documentation on the MySQL website.
SET PASSWORD FOR 'myuser'@'%.wherever.com' = PASSWORD('newpass');You can also read the documentation on the MySQL website.
Wednesday, December 03, 2008
Finding out what MySQL is doing
I was recently updating our production system when I noticed that our main MySQL database was going unusually hard. On a quad-core processor system, it was using up three processors simaltaneously. This prompted me to find out what MySQL is doing, and after being unable to read the logs, I did a Google search and ran across this useful little command :
This runs a query (can be run from either the command line or the query browser) and shows you a summary of all the processes that MySQL is currently using.
show processlist
This runs a query (can be run from either the command line or the query browser) and shows you a summary of all the processes that MySQL is currently using.
Subscribe to:
Posts (Atom)