Archive for February 2012
Accessing the Windows Registry via pure Java and the moral and technical failings of charlatans – Part 2
As mentioned in the last blog, here is the actual source code of the migration tool that I had written to migrate the folders from version 1 format to version 2 (with enough changes to preserve product anonymity, of course).
The tool consists of two files – RegistryReader.java and Main.java. RegistryReader.java is used to access the Windows Registry in read-only mode and extact the installation path of the product so that the absolute path of the reports folder(s) can be constructed using this install location as the base location. The other file, Main.java is the entry-point for the tool and contains logic to perform the actual migration (update and rollback). Both files are explained in detail separately, as follows.
package com.z0ltan.reports.enhancedmigrate;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
/**
* This class will access the Windows Registry to obtain the value of a
* specified 'key'.
*
* @author z0ltan
*
*/
public class RegistryReader {
private static final Logger logger = Logger.getLogger(RegistryReader.class
.getName());
// For Windows, the instance will be a WindowsPreferences instance
private static final Preferences userRoot = Preferences.userRoot();
private static final Preferences systemRoot = Preferences.systemRoot();
private static final Class<? extends Preferences> userClass = userRoot
.getClass();
private static final int HKEY_LOCAL_MACHINE = 0x80000002;
private static final int READ_INSTRUCTION = 0x20019;
private static final int SUCCESS = 0;
private static final int NOT_FOUND = 2;
private static final int ACCESS_DENIED = 5;
// Define the methods
private static Method openRegKey = null;
private static Method readRegKey = null;
private static Method closeRegKey = null;
/**
* Initialize the required methods statically.
*/
static {
try {
openRegKey = userClass.getDeclaredMethod("WindowsRegOpenKey",
new Class[] { int.class, byte[].class, int.class });
openRegKey.setAccessible(true);
readRegKey = userClass.getDeclaredMethod("WindowsRegQueryValueEx",
new Class[] { int.class, byte[].class });
readRegKey.setAccessible(true);
closeRegKey = userClass.getDeclaredMethod("WindowsRegCloseKey",
new Class[] { int.class });
closeRegKey.setAccessible(true);
} catch (SecurityException ex) {
logger.severe("Unable to get access to WindowsRegOpenKey method "
+ ex.getLocalizedMessage());
} catch (NoSuchMethodException ex) {
logger.severe("Unable to get access to WindowsRegOpenKey method "
+ ex.getLocalizedMessage());
}
}
/**
* This will read the specified key and return its value from
* HKEY_LOCAL_MACHINE.
*
* @param p6000paInstallPath
* @param p6000paInstallKey
* @return
*/
public static String getValue(String installPath, String installKey)
throws Exception {
String value = null;
try {
int[] openVal = (int[]) openRegKey.invoke(systemRoot,
new Object[] { new Integer(HKEY_LOCAL_MACHINE),
getStringBytes(installPath),
new Integer(READ_INSTRUCTION) });
if (openVal != null && openVal.length == 2) {
if (openVal[1] == SUCCESS) {
logger.info("Path " + installPath
+ " found on HKEY_LOCAL_MACHINE");
byte[] keyValue = (byte[]) readRegKey.invoke(systemRoot,
new Object[] { new Integer(openVal[0]),
getStringBytes(installKey) });
logger.info("Found the value for the key " + installKey
+ " on HKEY_LOCAL_MACHINE");
closeRegKey.invoke(systemRoot, new Object[] { new Integer(
openVal[0]) });
logger.info("Closed handle for Path " + installPath
+ " and Key " + installKey
+ " on HKEY_LOCAL_MACHINE");
if (keyValue != null) {
value = new String(keyValue).trim();
}
} else if (openVal[1] == NOT_FOUND) {
logger.severe("Path " + installPath
+ " not found on HKEY_LOCAL_MACHINE");
return null;
} else if (openVal[1] == ACCESS_DENIED) {
logger.severe("Access denied while trying to access Path "
+ installPath + " on HKEY_LOCAL_MACHINE");
return null;
} else {
logger
.severe("Unknown return code while trying to open Path "
+ installPath + " on HKEY_LOCAL_MACHINE");
return null;
}
} else {
logger.severe("Unable to obtain the value of Path "
+ installPath + " from HKEY_LOCAL_MACHINE");
return null;
}
} catch (IllegalArgumentException ex) {
logger.severe("Unable to obtain the value of Path " + installPath
+ " and Key " + installKey
+ " from HKEY_LOCAL_MACHINE. Reason = "
+ ex.getLocalizedMessage());
return null;
} catch (IllegalAccessException ex) {
logger.severe("Unable to obtain the value of Path " + installPath
+ " and Key " + installKey
+ " from HKEY_LOCAL_MACHINE. Reason = "
+ ex.getLocalizedMessage());
return null;
} catch (InvocationTargetException ex) {
logger.severe("Unable to obtain the value of Path " + installPath
+ " and Key " + installKey
+ " from HKEY_LOCAL_MACHINE. Reason = "
+ ex.getLocalizedMessage());
return null;
}
return value;
} // getValue
/**
* Convert the String value to bytes format taking care to ensure the '0' at
* the end.
*
* @param stringValue
* @return
*/
private static byte[] getStringBytes(String stringValue) {
byte[] bytes = new byte[stringValue.length() + 1]; // Add '0' at the end
for (int i = 0; i < stringValue.length(); i++) {
bytes[i] = (byte) stringValue.charAt(i);
}
bytes[stringValue.length()] = 0;
return bytes;
}
} // RegistryReader
Brief Explanation
The Preferences class in the JDK provides access to a wide variety of OS-specific functionality in Windows. An instance of the Preferences class is simply a node in a hierarchical collection of “preference” data backed up by a store. This store can take several forms depending on the actual OS at run-time – Registry, flat-files, directory servers or SQL databases. In the case of Windows, the concrete instance of this class is the WindowsPreferences object. This allows us to access the Windows Registry with consummate ease. As can be seen in the code above, the Preferences class is used to obtain a handle to the Windows Registry (There are two types of preference objects – User Preference and System Preference. For more information, read up on the official JDK documentation for the Preferences class). Once this handle has been obtained, we can use the Reflections API to invoke methods on this registry. In this particular case, only the query and retrieval API’s for the Windows Registry are used. However, the same approach can be taken to modify the state of the Windows Registry as well. The logic is pretty straightforward and the methods invoked are simply the official Windows Registry API’s documented here
Now onto the main migration logic.
package com.z0ltan.reports.enhancedmigrate;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
/**
* This class uses the RegistryReader to retrieve the product installation
* location and the processes the Reports folders for that installation.
*
* @author z0ltan
*
*/
public class Main {
private static final Logger logger = Logger.getLogger(Main.class.getName());
private static final String PRODUCT_INSTALL_PATH = "SOFTWARE\\Company-Name\\ProductInstaller";
private static final String PRODUCT_INSTALL_KEY = "InstallDir";
private static final String REPORTS_SUB_PATH = "\\ProductTools\\tomcat\\32bit\\apache-tomcat-6.0.24\\webapps\\product\\reports";
// For upgrade/rollback
private static final String REPORTS_TEMP_FILE_PREFIX = "Product_Reports";
private static final String REPORTS_TEMP_FILE_SUFFIX = ".XML";
private static final String REPORTS_FILE_NAME = "Product_Reports_Migration.XML";
/**
* The Main Man.
*
* @param args
*/
public static void main(String[] args) {
String installationDirectory = null;
if (args.length != 1) {
logger
.severe("Please enter the option - \"UPDATE\" or \"ROLLBACK\"");
System.exit(-1);
}
// Common part
try {
installationDirectory = RegistryReader.getValue(
PRODUCT_INSTALL_PATH, PRODUCT_INSTALL_KEY);
logger.info("Product Installation Path = " + installationDirectory);
if (installationDirectory == null) {
installationDirectory = "C:/Program Files/Company-Name/Product";
}
} catch (Exception e) {
logger
.severe("Unable to obtain the Product Installation Path. Proceeding with default Installation Path");
installationDirectory = "C:/Program Files/Company-Name/Product";
}
logger.info("Starting processing Reports Sub-Folders");
String reportsDirectory = installationDirectory + REPORTS_SUB_PATH;
if ("UPDATE".equalsIgnoreCase(args[0])) {
try {
Main.upgradeReportsSubFolders(reportsDirectory);
Main.deleteReportsMappingFile(new File(reportsDirectory
+ REPORTS_FILE_NAME));
} catch (Exception ex) {
Main.rollbackReportsSubFolders(reportsDirectory);
}
} else if ("ROLLBACK".equalsIgnoreCase(args[0])) {
Main.rollbackReportsSubFolders(reportsDirectory);
} else {
logger
.severe("Incorrect option supplied. Option can only be \"UPDATE\" or \"ROLLBACK\"");
System.exit(-1);
}
logger.info("Finished processing Reports Sub-Folders");
}
/**
* Process all the existing Reports folder in the format 'uSerName@HOSTNAME'
* to lower-case in compliance with Product 2.0 requirements.
*
* @throws Exception
*/
private static void upgradeReportsSubFolders(String reportsFolder)
throws Exception {
logger.info("Performing Reports Sub-Folders Upgrade");
// Set up the properties file environment
Properties writeProps = new Properties();
OutputStream os = null;
File tempFile = null;
String tempFileName = null;
try {
tempFile = File.createTempFile(REPORTS_TEMP_FILE_PREFIX,
REPORTS_TEMP_FILE_SUFFIX, null);
tempFileName = tempFile.getAbsolutePath();
// Actual processing starts here
File dir = new File(reportsFolder);
if (dir.exists()) {
if (dir.isDirectory()) {
File[] directories = dir.listFiles(new DirectoryFilter());
for (File directory : directories) {
File[] subdirectories = directory
.listFiles(new DirectoryFilter());
for (File subdirectory : subdirectories) {
String oldName = subdirectory.getName();
String keyName = directory.getName() + ":"
+ oldName;
writeProps.setProperty(keyName, oldName);
String newName = subdirectory.getName()
.toLowerCase();
File newFile = new File(subdirectory
.getAbsolutePath()
.replace(oldName, newName));
if (subdirectory.renameTo(newFile)) {
logger.info("Renamed " + oldName + " to "
+ newName);
} else {
logger.severe("Failed to rename " + oldName
+ " to " + newName);
}
}
}
} else {
logger.severe(reportsFolder + " is not a directory!");
}
} else {
logger.severe("Could not find directory " + reportsFolder);
}
//Now that all the sub-directories have been processed, store the
//original names into the XML file
os = new FileOutputStream(tempFile);
writeProps.storeToXML(os, "Original Reports Folders Mapping");
os.close();
File reportsFile = new File(reportsFolder + REPORTS_FILE_NAME);
tempFile.renameTo(reportsFile);
} catch (Exception ex) {
logger
.severe("A Fatal Error occurred while performing upgrade of Reports Sub-Folders. Reason = "
+ ex.getLocalizedMessage());
throw ex;
} finally {
if (tempFile.exists()) {
tempFile.delete();
logger.info("Temporary XML File " + tempFileName + " deleted");
}
}
logger.info("Performed Reports Sub-Folders Upgrade");
} // upgradeReportsSubFolders
/**
* Process all the updated Reports folder in the lower case format back to
* the original case form for instance, 'uSeRName@HOSTNAME', in compliance
* with Product 1.0 requirements.
*/
private static void rollbackReportsSubFolders(String reportsFolder) {
logger.info("Performing Reports Sub-Folders Rollback");
InputStream in = null;
File reportsFile = null;
try {
// Set up the properties file environment
Properties readProps = new Properties();
reportsFile = new File(reportsFolder + REPORTS_FILE_NAME);
if (!reportsFile.exists()) {
logger
.severe("Could not find the file storing the Original Reports Folders Mapping ("
+ REPORTS_FILE_NAME + "). Exiting!");
System.exit(-1);
}
// Actual processing starts here
in = new FileInputStream(reportsFile);
readProps.loadFromXML(in);
Iterator<Map.Entry<Object, Object>> mappingMapIterator = readProps
.entrySet().iterator();
while (mappingMapIterator.hasNext()) {
Map.Entry<Object, Object> entry = mappingMapIterator.next();
String key = (String) entry.getKey();
String arrayDirectoryName = key.substring(0, key.indexOf(":"));
String keyName = key.substring(key.indexOf(":") + 1, key
.length());
String folderFileName = reportsFolder + File.separator
+ arrayDirectoryName;
String value = (String) entry.getValue();
File folder = new File(folderFileName); // this will be
// in lower-case
if (!folder.exists()) {
logger.info("Reports Sub-Folder " + folderFileName
+ " does not exist! Skipping...");
continue;
}
// The Reports Sub-Folder exists, revert it to its original name
File currentReportSubFolder = new File(folderFileName
+ File.separator + keyName);
String curentReportSubFolderName = currentReportSubFolder
.getAbsolutePath();
File newReportSubFolder = new File(folderFileName
+ File.separator + value);
currentReportSubFolder.renameTo(newReportSubFolder);
logger.info("Renamed " + curentReportSubFolderName
+ " to its original name "
+ newReportSubFolder.getAbsolutePath());
} // while
} catch (Exception ex) {
logger
.severe("A Fatal Error occurred while performing upgrade of Reports Sub-Folders. Reason = "
+ ex.getLocalizedMessage());
} finally {
if (in != null) {
try {
in.close();
} catch (Exception exc) {
logger
.severe("Error while closing the InputStream stream while performing Reports Sub-Folder rollback");
}
}
Main.deleteReportsMappingFile(reportsFile);
}
logger.info("Performed Reports Sub-Folders Rollback");
} // rollbackReportsSubFolders
private static void deleteReportsMappingFile(File reportsFile) {
// Delete the Product_Reports_Migration.XML file
if (reportsFile != null && reportsFile.exists()) {
reportsFile.delete();
logger.info("Deleted the " + reportsFile.getAbsolutePath()
+ " file");
}
}
}
/**
* Simple Filter class to extract only the directories from the given list of
* Files.
*
* @author z0ltan
*
*/
class DirectoryFilter implements FileFilter {
public boolean accept(File file) {
if (file.exists() && file.isDirectory()) {
return true;
}
return false;
}
}
Brief Explanation
The Main class contains three API’s – upgradeReportsSubFolders, rollbackReportsSubFolders and deleteReportsMappingFile. The names are self-explanatory.
The basic flow is as follows – First off, the installation location of the product is extracted from the Windows Registry (since this location is user-configurable). The default installation location is used as a fallback in case the Windows Registry cannot be read or the key in the Windows Registry contains no usable value. From this base path, the absolute path of the Reports Sub-Folders is constructed for further processing.
If the user has chosen the ‘UPGRADE’ option, the upgradeReportsSubFolders API is invoked and all sub-folders in the main Reports folder are processed i.e., their names are converted to lower-case to be compliant with Product Version 2 format. For safety as well as rollback, the mapping of the original folder names to the their original cases is maintained in an XML file. Upon successful upgrade, this XML file is deleted as part of the clean-up. One point to note here is the usage of the temporary file during this processing stage – using a temporary file ensures the integrity of the XML mapping file. In other words, the atomicity of the file-population is ensured. If we were to use a normal file, any exceptions in between would leave the file in an indeterminate state! Thus once the XML file has been populated by the mappings, the temporary file is renamed to the actual XML file name. If the upgrade fails for any reason, the rollback API is called immediately.
If the user has chosen the ‘ROLLBACK’ option, the rollbackReportsSubFolders API is invoked. This API uses the XML mapping file to rename the folders to their original case-format. After this, the XML file is deleted since it is of no further use.
There you go – simple, elegant and efficient!
Accessing the Windows Registry via pure Java and the moral and technical failings of charlatans – Part 1
A week back I had completed a major feature for my product and I was looking forward to some quiet time to be spent on learning exciting new technologies and working on my side-projects. Alas, as the proverbial quiet before the storm, it was just premature wishing. My pointy-haired ‘technical lead’ lumbered down the aisle and plonked his massive load on my poor desk. He proceeded then to inform me that the test team had uncovered a major defect that should have been accounted for in the design for the first release of the product but somehow had slipped through (now if only the Test Team would use their brains as much as they are wont to use their gabs). So far so good.
The smug slithery gargoyle then proceeded to inform me of the fix that he had been working on over the week which he was ever so gracious to hand out to me. Okay, still not too bad – this level of micro-management is quite expected of immature boors. With a flourish of his corpulent hand he lisped, “You just have to convert the URL to lowercase and all will be well”. It was at this stage that I was caught between a sudden desire to make a shank out of my pen and dispatch him to the hellhole that had spawned him and a delirious impulse to laugh out hysterically. A veritable L’appelle du vide moment.
With considerable effort I steadied myself and told the His Grizzly Lordship that I would take a look into the issue and apprise him of my findings. The man did not take this well – it was an obvious affront to his infinite wisdom and with a sigh he plodded back to the dark recesses of his cubicle, his august countenance besmirched by a snarky sarcastic “You poor child” look. Thus begins the story.
A bit of background should do well at this juncture to clear the mind of the non-technical accoutrements laced on to this blog and move onto something a bit more technical. This is supposed to be a technical blog after all, innit?
The setup was this – the product had a feature to create reports for various objects of interest and export them in either PDF or CSV format. Now, the server would create the folders for these reports based on the user-name of the currently logged in user (after the pre-requisite security and permission checks, of course) and then populate these folders with the relevant reports. The user-name would be of the format ‘user@MACHINE‘. Now the user-name, by a quirk of the framework that we use, would allow any case i.e., ‘user@MACHINE‘ was essentially the same as ‘uSeR@maCHINE‘ or any permutation thereof. Unfortunately, Windows also did not care much for cases for folder or file-names. Thus a folder with the name ‘user@MACHINE‘ was again considered the same as ‘uSeR@maCHINE‘ by the OS. To further add on to this motley of case use-cases, the case did matter while generating a URL to be consumed by a browser (or, in the back-end, by some Connection based consumer such as the URLDataSource of the JavaMail API or Java’s very own Http(s)URLConnnection class). To summarize -
- The Report would be created in a folder on the file system with the same name as the currently logged on user.
- The authentication and authorization framework couldn’t care less for case-sensitivity.
- The report could be e-mailed and the URLDataSource class used to extract the report contents from the URL created for the report is case-sensitive.
- Windows does not care for case for folder and file-names.
- The server constructed the URL based on the Absolute Path Name of the location of the report and then appended the currently logged in user (say, ‘uSER@maCHINE‘) to this base path to construct the URL for the specific report. Ah, here lay the root of the problem!
In brief, suppose a user had logged in as ‘user@machine‘ and created a report, report1.pdf, for the first time, the sever would construct a folder named ‘user@machine‘ on the file system and then place the generated report in this folder. Now, suppose the user logs in as ‘USER@MACHINE‘ and then creates another report, report2.pdf, Windows would still find that the folder ‘USER@MACHINE‘ already exists and place the new report in the same folder as before, instead of creating a new folder with the name, ‘USER@MACHINE‘. When it comes to creating the URLs for these reports, the server would (as mentioned in the summary above) extract the base path, append the currently logged in user’s name to this path and then finally the report name to construct the final URL. Tomcat and the browser do not like this very much, as explained next!
The URLs for the two reports created above might look something like thus:
(report 1) https://localhost:{port}/MyWebProductContext/BaseFolder/reports/SYSTEM1/{user-name-goes-here}/report1.pdf,
(report 2) https://localhost:{port}/MyWebProductContext/BaseFolder/reports/SYSTEM1/{user-name-goes-here}/report2.pdf
The following situations can arise:
- The user has logged in as ‘user@machine‘and tries to access report1.pdf (the GUI constructs the URL is much the same manner as the server, don’t ask me why the duplication!) and download it via the hyperlink in the GUI (which, for those interested, is written in ActionScript) – no problem. E-mail works fine as well since it can access this URL.
Thus also the case where the user is logged in as ‘USER@MACHINE‘ and tries to access report2.pdf which was created in the session with this user-name
- The user has logged in as ‘user@machine‘ and then logs out for some reason and logs in as ‘USER@MACHINE‘. The framework considers him to be the same user as ‘user@machine‘ and when he tries to download report1.pdf or e-mail it, the whole beautiful dream comes crashing down! The server complains that it cannot find the URL:
https://localhost:{port}/MyWebProductContext/BaseFolder/reports/SYSTEM1/USER@MACHINE/report1.pdf
Is this behavior correct? Most certainly so! This is because there is a folder named ‘USER@MACHINE‘ on the system but there is no USER@MACHINE/report1.pdf to make a connection to! Now, the subtlety is that for Windows and for the Java File I/O API’s this is not true – ‘user@machine‘ is the same as ‘USER@MACHINE‘. Unfortunately, for Tomcat Server, while constructing the URL, it does matter – it is simply unable to find the aforementioned URL! JavaMail‘s URLDataSource also complains that it cannot read from this URL’s connection. Ultimately the root issue for both Tomcat and JavaMail might be the fact that Java’s HttpURLConnection/HttpsURLConnection classes cannot read from a connection created to these URLs. Sigh, this reminds me of my earlier post, .
To be completely pedantic, ActionScript‘s File I/O is also broken when it comes to reading from a URL, as is the case for GUI access to reports for this product.
- The user is currently logged on as ‘user@machine‘ and tries to access report2.pdf. The URL for report2.pdf is as follows (in this session):
https://localhost:{port}/MyWebProductContext/BaseFolder/reports/SYSTEM1/user@machine/report2.pdf
This is essentially the same case as the previous case and would not work.
The brief of all these complex permutations of use-cases is that ultimately the product was broken in the sense that a user could only access/e-mail those reports that were created whilst logged on as exactly the same user-name, case-wise. Any other case would not work. This was a gross oversight in the design of the feature and the best thing to be done was to salvage what we could of this feature.
The Solution?
The solution was indeed as simple as ensuring that whenever a URL was constructed for reports, the user-name of the currently logged on user was always converted to a consistent case-system (lower-case was chosen) so that all parties were happy – Tomcat Server, JavaMAIl URLDataSource, the GUI and the user, of course. Now it may seem ironic that I was denigrating the Custodian of the Ivory Tower (read the ‘technical lead’) all this while but ultimately came to the same conclusion myself! Not quite. Again, in point form (oh, how we developers love being logically organized, heh):
- His suggestion was to convert the server-side URL to lower-case. A weekend of research and he had no idea that the GUI constructed its own URL. It would have resulted in a fragmented brittle and inconsistent product.
- This one takes the cake, no really – consider that this is the second release of the product and the damage done by the original developer of this module was already done. My two-pronged solution of fixing the Server and GUI URLs fixed the problem for this release i.e., for fresh installations. What about the customers who were already using the first release? How on Earth would they migrate their reports, which might number in the hundreds if not the thousands?
With this in mind, I wrote a simple tool in Java to perform the migration of existing reports. Basically when the customer runs the installer for the latest version of the product, an update should be allowed. As part of this update process, my migration tool would hum along nicely and convert all the existing reports folders on the customer’s machine to lower-case. And just in case you’re wondering, yes, in the case of a failed update or a user-triggered roll-back, the tool is intelligent enough to be able to rename the folders back to their original case-formats! However, this is material for the next blog.
- Armed with this elegant and yet simple solution, I proceeded to explain the various scenarios and my solution to all of them, to the lead. His reaction had to be seen to be believed! He turned crimson in the face and was incredulous that I had not only not heeded his original hand-out of a solution but also had had the temerity to devise a migration tool without his permission! Haha. The worst part was that he could not genuinely comprehend why the migration tool was required and why his solution was broken. The scene was comical and I would be laughing at it over a beer if not for the fact that it was also highly unbecoming and unprofessional of a lead with so much of experience (on paper) to be childishly arguing with reason itself! I was busy with another feature at that point of time and after a full minute of me going, ‘It’s not going to work!’ and he replying, ‘No, it will work!’, I gathered my calm and told him, ‘Okay, I am working on this urgent feature and I have no time to argue anymore if you’re not willing to listen. Please check the mail with all the details and the attached JAR file with the migration code and revert if you need any further details’. And I sat down and got busy with productive work.
A day later, the lead came up to me and sheepishly whispered, ‘Please contact the install team to see how best to integrate your tool with the installer for this release’. That was it! Not even a kudo or a ‘good job’ but that was hardly expected. The real satisfaction for me was in the realization that I had far more depth of technical knowledge, ability to consider various angles of a seemingly simple situation, the integrity to do a fine job and hold onto my ground in what I believed and delivering superlative-quality solutions to the problems at hand. It gave me a ton of confidence in my abilities and also a real taste of how to deal with real-world charlatans. Take heed, my friends, pointy-haired buffoons are not just of the managerial type! Heh.
Next blog – the actual code for accessing the Windows Registry and performing the updates and roll-backs demonstrating the wonderful storeToXML API of the Java Properties class.
The blogging moratorium is over! And what a relief that is…
I had a whole bunch of interesting problems that I had been working on lined up to be blogged about. Unfortunately, as it often does, providence decided to lay obstacle after obstacle on the selfsame path, mostly of the managerial sort if I may add. On top of that, some utterly incredulously epic ineptitude shown by my ‘technical lead’ (more like ‘technical lard’).
Anyway, here is a bunch of items that I have in mind, to be dealt with over the next few days:
- The whole PostgreSQL 8.3 to PostgreSQL 9.0 migration (continued on from last time) fiasco and how it actually helped me crack a whole bunch of critical customer issues.
- A whole series of commentaries on my experiences with the Quartz Scheduler and the gotchas that I learnt along the way.
- A really interesting side-project that I had done recently which involves accessing the Windows Registry via pure Java and also demonstrates the aforementioned crass mental limitations of the said technical lead.
- Thoughts upon and planned projects for the Arduino (actually Freeduino in my case).
- ActiveMQ, JMS, RabbitMQ and the entire Messaging domain (this has been long in the making and is something that I am going to relish writing about!).
And of course, a whole bunch more. Stay tuned!