Thursday, July 31, 2008

Cuil not so cuil

It's been done to death already but I can't help agreeing with the pundits who say Cuil will not replace Google as the search engine of choice. I did a search for "Doogal Bell" and most of the returned sites were search listings on other sites that linked to my stuff. OK, I could get to my stuff indirectly this way, but it's hardly what I'm after. Doing a search on Google brought up this site, my home page, the Random Pub Finder and my photos on flickr, which is what I'd expect. It did throw up a few useless directory and search listings sites but further down the list. OK, not very scientific, but it's very rare Google gives me unhelpful results so it's still relevant I think.

The Cuil website says "Rather than rely on superficial popularity metrics, Cuil searches for and ranks pages based on their content and relevance". Isn't this the largely discredited method of searching the web that was used by search engines before Google came along? So how's it going to be better now? It was way too easy to game those search engines by just repeating keywords in the text of your page.

Anyway, it'd be nice if somebody did come up with a decent alternative to Google, it's becoming increasingly obvious that they will soon be a monopoly and we all know what monopolies start getting up to...

Tuesday, July 22, 2008

Radiohead - Ceremony

One of my favourite bands* covering one of my favourite songs, what's not to like?

And if you like it, download it with KeepVid (this is mostly a reminder for myself, I'm sure you already know about this website since you're probably much more web savvy than me)

* If you've read one of my previous posts, you may think I don't like them anymore, but their last album has mostly restored my faith. As has this cover.

Saturday, July 19, 2008

Tracking down memory leaks in managed code

I've been spoilt in the past when I've had memory leaks in my applications. I've worked at places where AQTime has been readily available. But I've recently noticed what appeared to be a memory leak in the FreeFlow Administrator and given that it's a free application I can't justify spending cash on a memory profiler. I had a search on the internet for a free memory profiler and downloaded a trial version of .NET Memory Profiler from Scitech. This was pretty sweet but when the trial ran out I was again unable to justify spending money on buying it.

I then thought there might an API available to capture the objects allocated by the .NET runtime but I was unable to find one. I'm guessing there has to be one, since otherwise how would memory profilers work? Perhaps the API is unmanaged or isn't documented, either way I was unable to find it. But what I did find was useful none the less. Shipping with the .NET runtime is a DLL called SOS.DLL that can be used from Visual Studio to debug memory leaks. I won't go into configuring Visual Studio to use the DLL since this post covers this in detail (the post talks about Visual Studio 2005 but it works just as well in 2008). Instead I'll cover the steps needed to track down a memory leak.

First up, set a breakpoint in your app. When you hit the breakpoint and are in the debugger, these are some useful commands that can be typed into the Immediate window.

.load sos - this loads the SOS.DLL so you can now start to use the SOS commands

!DumpHeap -stat - this will show a list of objects created, grouped by their type and ordered by the total amount of memory used by the objects. You need to analyse this list and look for anything that looks suspicious. I generally ignore the .NET types and concentrate on my own types, since it's most likely that these are causing the problems. Also, it's pretty hard to figure out how many .NET type instances would be too many, there may seem to be lots of string objects around but how many should there be? I was suspicious about a class called FolderControl, so decided to dig deeper into the details for that class.

!DumpHeap -type <type name> - this shows the details of each instance of the specified type (you don't need to specify the fully qualified type name, just the class name will do). The most important detail listed here is the address, since this will be used in the next command.

!GCRoot <address> - this looks for references to an object, which can help track down why an object isn't being garbage collected and hence causing a memory leak. I found the output somewhat confusing but I guess it may be useful.

!help - this lists all the commands available. There are many more than the ones mentioned above.

!help <command name> - get more information about a specific command.

In my case, my dynamically created control was hooking into the Application.Idle event and never unhooking the event handler. Since the Application object remains active for the lifetime of the application, my control was never garbage collected. In my experience, a lot of memory leaks in WinForms applications are caused by event handler issues like this.

After all that, I feel like a proper hardcore geek. Time to learn some assembler...

Wednesday, July 09, 2008

IE7 vs FireFox : The never ending debate

I was reading a forum today when I spotted yet another debate about FireFox and IE7. Much like the Windows vs Mac vs Linux debate, this is getting incredibly tedious. Both browsers (along with Safari, Opera et al) do a perfectly good job of letting people browse the web. Both have their plus points (e.g. FireFox has better plug-ins, IE tends to work with more sites) so just let it go, will you?

Much like the OS debates, the thing that really gets my goat is the smug superiority of FireFox users. Look, using a minority web browser does not make you a hip happening person, it just means you use a different browser than the majority of people. It doesn't make you a better person just because you're not using software written by the "evil empire".

For the record, I use IE and FireFox and I'm not hugely excited by either...

Sunday, July 06, 2008

Zoopla - How much is your house currently worth?

Yet another site for the British obsession with house prices. When I first looked, our house was apparently worth £371,039, now it's worth £375,350, who says house prices are falling? (well me for one...). I'm not sure how valuing it to the nearest pound is possible but hey who am I to argue. Perhaps this is the answer for people having trouble selling their homes, put them on the market for a very exact price, thus showing buyers that you're not messing about and know the value of your house precisely. Well, perhaps.

Any talk of the massive inflation in house prices always makes me think of the quote "House prices are a matter of opinion but debt is real". Even though the value of our house has apparently increased way beyond inflation in the 8 years we have been living here, the debt hasn't reduced dramatically. And due to the way mortgages work, we still have to pay the same amount every month. In fact, come November, we'll probably have to pay a whole lot more. So, why is house price inflation good exactly?

Thursday, July 03, 2008

How to escape text in SQL statements

Two things got me thinking about escaping text in SQL statements recently. I had previously thought it was a simple topic that everybody already knew about but given that these two things occurred I can only assume that not everybody knows about why escaping text in SQL is important and how to do it. First people keep ending up on the FreeFlow website because one of the methods in the class library is called SqlEscape, so obviously people want to know how to do it. The implementation is very very simple.

    /// <summary>
    /// Escapes a string so that single quotes are replaced by two quotes for use in SQL expressions.
    /// </summary>
    /// <param name="sql">The string to escape.</param>
    /// <returns>The escaped string</returns>
    public static string SqlEscape(string sql)
    {
      if (sql == null)
        return null;
      return sql.Replace("'", "''");
    }

Second, I got hold of some free PHP code off the web that failed to do any escaping of the text in SQL expressions. I foolishly deployed this to a website without checking the code beforehand. Fortunately I managed to spot the error before any dodgy hacker managed to take advantage of it. In the PHP world, escaping can be done using the mysql_real_escape_string function.

So that covers the how but why is this important? The first, probably least important, reason is that it means queries that include ' in text strings will work. I guess most people come across this problem when they try to enter a name like "O'Connor" into an application and it fails.

The more important issue, particularly with public facing websites, is SQL injection where a hacker can manage to run pretty much any query they like against your database.

There are other ways to solve this problem. Stored procedures and parameterised queries will also do the trick.

The joy of WITH (NOLOCK)

For a long while I thought it odd that reading from a SQL Server database caused the rows that were being read to be locked. Thinking about it for a little longer makes it clear that it's a necessary evil. You don't want some other process to update records as you're reading them, since the results you get back may well be inconsistent. But the thing is this often doesn't matter that much, so long as it's near enough correct. I've only recently come across WITH (NOLOCK) and I think it's wonderful. It's a quick and dirty way to boost the performance of your queries since it means the query can execute without acquiring a lock on the rows you're trying to get hold of.

Opponents of using it will say there are more fundamental problems with your database that could be fixed with some better indices, but sometimes we don't have the time to do proper performance analysis, so a quick fix is welcome. Purists will no doubt look down their noses at using this technique since it's just not the right thing to do. But I don't care, it improves performance for very little cost. Saying that, I do wonder how inconsistent the data may be. For instance, could I read a row that has a half-written text field? I haven't seen anything like this yet but I do wonder if it's possible.

Friday, June 27, 2008

RIP Elmo

Yesterday our pet chinchilla Elmo bit the dust. He had been ill for several weeks. He'd stopped eating and a visit to the vet and some drugs didn't help the little fella.

At least I wasn't to blame for this death unlike our last chinchilla, who I accidentally stood on as he ran down the stairs. A similar fate befell my brother's cat who ended up under the wheels of his car.

I was worried how our daughter would handle his death, but in fact she was very cool about the situation, mostly concerned about when we'd be getting another pet...

Thursday, June 26, 2008

IE8 not ready for prime time

I downloaded and installed IE8 beta 1 primarily to have a look at the new XDomainRequest object which allows for cross-domain requests. Unfortunately that old problem security gets in the way. Although the documentation implies it is possible to make requests to any server, it appears the server has to explicitly support the new request protocol. Ho hum.

Anyway, IE8 screws up lots of pages (most notably Google Maps) and although it claims to have an IE7 emulation mode, using this mode seems to make no difference at all, as far as I can see. So time to uninstall, at least until the next release...

Tuesday, June 24, 2008

IE6 is dead, long live IE7

IE7 usage I'm not one for making predictions. OK, I've made a few in my time but I'm invariably wrong. Predicting that the majority of IE users would be using version 7 within two months was one of my worst. Here we are, over 18 months since it was released and IE7 visitors to the Random Pub Finder have finally started to outnumber IE6 users. So I guess in another 18 months I can start to forget about supporting IE6. Oh, and worry about IE8 instead, sigh... 

Sunday, June 22, 2008

How to use Experts Exchange

I don't know about you but a lot of my web searches bring up Experts Exchange articles. When you first look at the page, it looks like you have to register to see the responses to the query. In fact it says "All comments and solutions are available to Premium Service Members only". But look a little further down the page (OK a lot further) and you'll see all the responses without paying a thing.

It may seem surprising that the comments are all visible to anybody who hasn't registered, kind of destroying their whole business model. But I guess they have a problem. I'm guessing most of their traffic comes from search engines so they need to have some decent content to get visitors in. To do that, they need to include all the responses. They could hide the text from the browser whilst making it visible to search engines, but this would almost certainly get them removed from the search engine listings, since this would be considered as black hat SEO. So they have to include the responses visible to everybody and just hope they get enough suckers signed up with the huge blocks of text before you get to the real content.

Sunday, June 15, 2008

Metastorm BPM 7.6 and Windows Workflow part 3 - Using Visual Studio

In the first two parts of this series I've given a brief introduction to the integration of Windows Workflow into Metastorm BPM 7.6. For this final part I'll be discussing what interests me, being able to execute workflows authored in Visual Studio from Metastorm BPM.

The first question was, is it even possible? There is no UI for publishing workflows to the Metastorm database, other than those authored in the Metastorm WF Composer. But looking at the database, it looked like the only tables that needed populating were eMSWorkflow and eMSWorkflowDefinition. After some playing around I came up with the following code, a simple command-line tool. This is by no means bulletproof, it will fail if the workflow has already been published (the next version of the FreeFlow Administrator will let you delete workflows) and will likely fail for a host of other reasons. I was using it purely as a proof of concept.

using System;
using System.Data;
using System.Data.Odbc;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Workflow.Activities;

namespace PublishWorkflow
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 4)
{
Console.WriteLine("PublishWorkflow usage:");
Console.WriteLine("PublishWorkflow [ODBC DSN] [ODBC username] [ODBC password] [Workflow DLL] ");
}
else
{
string fileName = args[3];

// get the assembly
Assembly assembly = Assembly.LoadFrom(fileName);

// get the workflow type
Type workflowType = null;
Type[] types = assembly.GetTypes();
for (int i=0; i<types.Length; i++)
{
if (types[i].IsSubclassOf(typeof(SequentialWorkflowActivity)))
{
workflowType = types[i];
}
}

if (workflowType == null)
throw new Exception("Can't find a workflow in the assembly");

string connectionString =
string.Format("UID={0};PWD={1};DSN={2}", args[1], args[2], args[0]);
using (OdbcConnection connection = new OdbcConnection(connectionString))
{
connection.Open();

Guid guid = Guid.NewGuid();

// write to eMSWorkflow
using (OdbcCommand command = connection.CreateCommand())
{
command.CommandText = string.Format(
"INSERT INTO eMSWorkflow (eWorkflowName, eWorkflowGuid) VALUES " +
"('{0}', '{1}')",
workflowType.FullName, guid.ToString());
command.ExecuteNonQuery();
}

// write to eMSWorkflowDefinition
using (OdbcCommand command = connection.CreateCommand())
{
string fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location).FileVersion.ToString();

byte[] buffer;
using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read))
{
buffer = new byte[stream.Length];
stream.Read(buffer, 0, Convert.ToInt32(stream.Length));
stream.Close();
}

command.CommandText = string.Format(
"INSERT INTO eMSWorkflowDefinition (eWorkflowGuid, eWorkflowName, eFileVersion, eFullyQualifiedTypeName, " +
"eFullyQualifiedAssemblyName, eWorkflowDefinitionType, eRulesDefinitionName, eLoadedTime, " +
"eWorkflowDefinition, eWorkflowProject) " +
"VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', 'A', '', GETDATE(), ?, NULL)",
guid.ToString(), workflowType.FullName, fileVersion, workflowType.AssemblyQualifiedName,
assembly.FullName);
OdbcParameter parameter = new OdbcParameter();
parameter.Value = buffer;
parameter.DbType = DbType.Binary;
parameter.Direction = ParameterDirection.Input;
command.Parameters.Add(parameter);
command.ExecuteNonQuery();
}
}
}
}
}
}

So I published my simple Visual Studio authored workflow using this code and was able to execute it. It would appear the process context activity provided as part of the Metastorm activities isn't required, although I assume it will be needed if you need to get folder information into your workflow.


The next step was to see if I could execute a workflow that contained custom activities. Obviously the problem here is where will the engine pick up the required assemblies (assuming the custom activities are in their own assembly)? I tried putting the DLL in the engine directory, dotnetbin directory and even the System32 directory (since this is where dllhost lives) but none of them seemed to work. All I got was the following useful error message


Microsoft Workflow evaluation (Instance ID 963fc00a-f2f9-40a8-a0df-7111b0706cd7)
For more information refer to the workflow tracking and event tables.

In fact the tracking and event tables contained no useful information. This error message seems to be the default error message when something goes wrong with a workflow and I've never found any useful information in the database. Debugging the engine through Visual Studio seems to be the only way of getting hold of the real error.


In the end the only way I found to get the engine to pick up my custom activity assembly was to install it in the GAC. Once I did that the workflow executed correctly.


So yes it is possible to execute a VS authored workflow in Metastorm, although some frigging around is required. However I have one concern. One problem I encountered whilst researching this was that workflows would abort after a certain time. Once again, the error above was shown in the Designer Log. Debugging the engine showed the problem was the workflow was timing out. I found a registry setting that controls this timeout, which by default is set to 60 seconds. But how does this timeout work? Is a thread kept alive waiting for the workflow to terminate? If so, this will be a problem for scalability if you wish to execute long running workflows. And that is what Windows Workflow is all about, running some code, sleeping for a week, executing some more code etc. I don't have the answer to this question yet, but will investigate further.


Part 1 - The basics

Part 2 - The database tables

Part 4 - Using your own activities

Part 5 - Long running workflows

Part 6 - State machines

Tuesday, June 10, 2008

Fixing Flash problems in IE7

I've had this for a while, some of the Flash content on the BBC website wouldn't play (although this is better than the problems I had with their previous Real Player content which always seemed to kill my wireless router). I was told my version of Flash wasn't up to date. Re-installing Flash didn't make any difference and following the suggestions here didn't help either.

So I decided to have a closer inspection. I had a hunch it may have something to do with my user agent string, since Flash content worked in some places (including the BBC iPlayer, go figure). So I checked my user agent using the following HTML page

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Untitled Page</title>
</head>
<body onload="javascript:alert(navigator.userAgent)">

</body>
</html>

Weirdly this showed my browser as IE6... Which was odd. Searching a bit further I came across this Microsoft article on user agent strings. BTW, the suggestion to type javascript:alert(navigator.userAgent) into the address bar didn't work for me, my guess is this was a security hole waiting for an exploit so has been disabled. Anyway I had a look in my registry to see what was happening and it turned out I had a registry key under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\User Agent\Post Platform. Part of it pointed to bsalsa.com. I guess I must have installed some of their stuff some time ago though lord knows why they need to fiddle with user agent strings.

Anyway, after deleting this registry key and a restart of IE my user agent string returned to something more sensible and BBC Flash content suddenly sprang into life. Not only that but I can now login into my Fidelity account as well, although that's not necessarily such a good thing given the current state of the stock market.

Update - Hmm, the registry entry has re-appeared, so one of the apps I use quite regularly is writing to that spot in the registry. Next step is to figure out which app it is...

Update 2 - Looks like PHPEdit was responsible, an app written in Delphi which presumably uses one of the components that come from bsalsa.com. I guess any programs using the component may cause the same problem. Getting the latest build from their website fixed the problem.

Sunday, June 08, 2008

Metastorm BPM 7.6 and Windows Workflow part 2 - the database tables

In part 1 I discussed the new Windows Workflow features in Metastorm BPM 7.6. I'll now move onto the database tables used by WF workflows in Metastorm.

First we have two tables that contain process metadata.

eMSWorkflow is pretty simple, it contains one entry for each workflow published to the database. There is a column for the name of the workflow and a GUID column. The name is the name that will be used when executing a workflow from Metastorm. I'm unclear what the GUID column is for. Multiple workflows with the same name aren't possible so it isn't for differentiating between two workflows with the same name.

eMSWorkflowDefinition holds an entry for each workflow version published to the database. Most of this table is pretty self-explanatory. Again there is a GUID column whose purpose isn't clear. There is an interesting column called 'eWorkflowDefinitionType'. I haven't investigated this too far but it suggests that it is possible to publish workflows as assemblies or as uncompiled XOML file.

Next we have tables that hold workflow instance data.

eMSWorkflowTracking contains audit trail information for executing workflows. It looks like Metastorm have implemented their own tracking service which makes sense since it means the workflow audit trail can be connected to the Metastorm folder's audit trail. One problem I have with the current implementation is there appears to be no way to connect a workflow instance to its type, so it will be difficult to figure out how many instances of a particular workflow type are in existence.

eMSWorkflowEvent also includes data about workflow instances. The difference is that this table contains information that is related to the workflow runtime, rather than the workflow itself. So it will contain entries for when an instance has been loaded or idled etc.

Finally there are two other tables. These are the standard tables created for the WF persistence service, CompletedScope and InstanceState. The Microsoft documentation for these tables seems to be pretty much non-existent, so I don't have much idea what is in there. All I can assume is the 'state' column contains the serialized data of the current state of the workflow instance.

So that's the database tables. Part 3 will discuss running workflows generated in Visual Studio (if I get time to actually get it working).

Part 1 - The basics

Part 3 - Using Visual Studio

Part 4 - Using your own activities

Part 5 - Long running workflows

Part 6 - State machines

Thursday, May 29, 2008

How record companies can save themselves

I got a lovely book for Christmas, "Factory Records - The Complete Graphic Album", which shows off the artwork for most of the albums and singles that came out on the Factory label along with other artwork used in ads etc. It got me thinking about why people are not buying music from record labels much anymore, instead choosing to download it free over the internet. Of course, one reason for this is the price but I think there's more to it than that. People have always taped music from friends but they still bought it as well.

But look at that Factory artwork again, some of it was superb (and some of it wasn't...). But buying a Factory record wasn't just about music, it generally came in a beautiful package as well. In the days of vinyl, there were little messages in the run-out groove (it wasn't just Factory that did this of course). Being 12" wide meant the artwork could be much more complex. So record companies should go back to vinyl? No, though they could certainly think about it. But the point is that when I bought music in those days I felt a connection with the artist or record company because it felt more than "product", it was sometimes a work of art.

Now if I pop down to a music shop most of the racks seem to be populated with CDs that look like they had absolutely no thought put into their production. Plastic case, crap photo of the artist, no design aesthetic. Why not download it for free? What do I gain by paying a tenner?

But it doesn't have to be this way. I just bought the boxed deluxe edition of U2's "The Joshua Tree". OK, I know, self-important and pretentious, but I do think it was a great album so I'll overlook the earnestness. For just over twice the price of a normal CD, I got 2 CDs (the album and a CD of rarities), a DVD (live concert, documentary and videos), a book and some postcards. All in beautiful packaging. I snapped it up without a second thought. Presumably Island are making money out of this, so why can't other recorded music come like this? Give people a reason for buying your music and they may well be persuaded.

Tuesday, May 27, 2008

Adding code to a XOML workflow

I was aware that it was possible to add code to a XOML workflow but I hadn't actually seen any examples of it, until I started looking into the Metastorm WF integration. I'm not sure it's something I'd like to do, it's pretty ugly and I like the idea of producing a workflow definition in a purely declarative manner. That way you're forced to put real code in their own activities which I think helps the overall design of your system. However there is one place where it isn't possible to do everything in XML, when you need to add a property to a XOML workflow. So here's an example of that. If you get stuck using this, you're on your own, I don't know much about it and the documentation seems a bit thin on the ground. Here's hoping that MS add support for XOML properties in the next release...

<x:Code><![CDATA[//<?xml version="1.0" encoding="utf-16"?>
//<XCodeItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="XCodeProperty">
//  <MemberTypeName>System.Boolean</MemberTypeName>
//  <MemberName>Thingy</MemberName>
//  <EmitDependencyProperty>true</EmitDependencyProperty>
//  <IsMetaProperty>false</IsMetaProperty>
//  <IsAttached>false</IsAttached>
//  <IsReadOnly>false</IsReadOnly>
//</XCodeItem>


public static System.Workflow.ComponentModel.DependencyProperty ThingyProperty = DependencyProperty.Register("Thingy", typeof(System.Boolean), typeof(MyWorkflow));

public bool Thingy
{
get
{
return ((bool)(base.GetValue(MyWorkflow.ThingyProperty)));
}
set
{
base.SetValue(MyWorkflow.ThingyProperty, value);
}
}
]]></x:Code>

Monday, May 26, 2008

Metastorm BPM 7.6 and Windows Workflow part 1 - The basics

The new version of Metastorm BPM, 7.6, has several new features (and I may do a full review at some point), but the one that interests me the most is the integration with Windows Workflow. This has been a fairly long time in development, a screenshot appeared on the web about 18 months ago. The first WF integration appeared in 7.5, which allowed BPM functionality to be called from WF workflows. This was not of great interest to me as it always seemed more useful to be able to call WF workflows from Metastorm. This is what has been added in 7.6.

Installation is easy, although a complete re-install is required, rather than an in-place upgrade. An upgrade will update the 7.5 pieces, but will not install the new 7.6 pieces.

The main thing of interest is the new WF designer (called WF Composer) that is used to develop and publish WF workflows to the Metastorm database. It's quite a nice tool, but my main gripe is that it's not Visual Studio. If it's aimed at developers then integration with Visual Studio would be a more preferable solution. Metastorm don't have the resources to produce an alternative to VS and hence any WF designer they produce will always look poor in comparison. Perhaps developers aren't the target audience, but if not then why does it include a C# editor? This is also much less useable than the Visual Studio code editor.

Another problem is that it isn't possible to add your own activities into the Designer. I'd always imagined WF could be used by a non-technical person, but to achieve this would require developers to go off and write activities specific to their organisation, that could then be plugged together by the non-technical people. So to my mind, the Composer isn't suitable for a non-technical person either.

To be fair, the Composer does add some functionality to make life simpler. When creating a workflow, it adds the Process Context activity, which provides access to the data in the BPM folder. It also simplifies adding properties to the workflow, since you can add a property just by selecting a name and a type (much like adding custom variables in Metastorm BPM). This then adds the relevant code to the generated workflow. It also includes some activities that implement familiar Metastorm BPM functions such as Email, Manager, SelectSQL etc. Not all functions are wrapped as activities but the rest can be accessed via the generic EvaluateFunction activity.

Executing workflows from BPM is pretty straightforward. Add the Workflow Support Library to a procedure and then use the Integration Wizard to execute the workflow synchronously or asynchronously. One thing to consider when executing a long-running workflow is how you're going to track its life time. It will have to be executed asynchronously since a synchronous execution will lock up your BPM folder and will also likely time out. You can get hold of the ID of the WF instance when you execute it and the tracking data is stored in the Metastorm database so it should be possible to get hold of the tracking data to display in a Metastorm grid. If your WF workflow must complete before your BPM workflow can continue, you'll probably need to add a timed loopback action to keep checking until your workflow is complete. Alternatively you could send a message back from the WF workflow to inform the Metastorm folder it has finished. There is unfortunately no user interface to view WF instances which could make debugging more difficult.

That all said, I am still interested in getting WF workflows executing in Metastorm BPM. This could be very useful so we can re-use functionality in different workflow environments and even help us to migrate away from Metastorm BPM when we are looking for a cheaper solution. But given that I would like to use Visual Studio to author my workflows how do we get them into Metastorm? I'm going to investigate this and will report back in part 2.

Part 2 - The database tables

Part 3 - Using Visual Studio

Part 4 - Using your own activities

Part 5 - Long running workflows

Part 6 - State machines

Thursday, May 15, 2008

No End in Sight: Iraq's Descent into Chaos

Iraq doesn't get in the news much currently, with natural disasters and global financial problems overtaking it in the headlines. But a quick search of Google News shows plenty of people are still dying there. So 'No End In Sight' is still as relevant now as it was when it came out. And a quick précis of the file would be the US (and the UK of course) did one thing right, getting rid of Saddam, then screwed up everything possible after that. It doesn't bother to delve too deeply into the lies that led up to the war but looks at the aftermath instead. First there was no plan, then the plan that was hatched was insanely stupid and it's no surprise that the carnage carried on for so long. Get rid of the army to leave 500,000 soldiers with no work and with a grudge? Brilliant...

The director, Charles Ferguson, has made an unusual change of career. He started out in software, running the company that created FrontPage (and writing an entertaining book about it). Nice to see him put his millions to good use.

Monday, May 12, 2008

Absolutely

It's odd that I watched pretty much every episode of Absolutely when it was on and yet I'd completely forgotten about its existence until I came across an article about it in an obscure South London listings magazine. Perhaps not surprising since it being on telly coincided with my time at university when I was mostly drunk. Anyway, all 4 series are now available on DVD. Go and buy it now.

Monday, May 05, 2008

Synchronous execution of a child XOML workflow

Previously I came up with a reasonable approach to executing a child XOML workflow asynchronously but what I was really after was executing the workflow synchronously. I eventually realised that the solution to this problem was to fire up another WorkflowRuntime and execute the workflow using that. I now have a WorkflowExecutor class as below.

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;
using System.Xml;

namespace WFBuild.Activities
{
  /// <summary>
  /// Executes a workflow synchronously
  /// </summary>
  public class WorkflowExecutor
  {
    private AutoResetEvent waitHandle;
    private Guid wfGuid;
    private Exception ex;

    public void Execute(string fileName)
    {
      WorkflowRuntime workflowRuntime = new WorkflowRuntime();
      workflowRuntime.StartRuntime();
      workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
      workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);

      FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
      XmlTextReader reader = new XmlTextReader(stream);
      WorkflowInstance instance;
      try
      {
        instance = workflowRuntime.CreateWorkflow(reader);
      }
      catch (WorkflowValidationFailedException exp)
      {
        StringBuilder errorMsg = new StringBuilder();
        errorMsg.AppendLine("Validation failed:");
        foreach (ValidationError error in exp.Errors)
        {
          errorMsg.AppendLine(error.ToString());
        }
        throw new Exception(errorMsg.ToString());
      }
      wfGuid = instance.InstanceId;
      waitHandle = new AutoResetEvent(false);
      instance.Start();
      waitHandle.WaitOne();

      if (ex != null)
        throw ex;
    }

    private void workflowRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
    {
      if (e.WorkflowInstance.InstanceId == wfGuid)
      {
        // pass the problem back to the main call
        ex = new Exception("Workflow terminated - " + e.Exception.Message);
        waitHandle.Set();
      }
    }

    private void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
    {
      if (e.WorkflowInstance.InstanceId == wfGuid)
      {
        waitHandle.Set();
      }
    }
  }
}

This code is called from the activity like so

using System.Workflow.ComponentModel;

namespace WFBuild.Activities
{
  public class InvokeXomlWorkflowActivity: Activity
  {
    private string xomlFile;
    public string XomlFile
    {
      get { return xomlFile; }
      set { xomlFile = value; }
    }

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
      WorkflowExecutor executor = new WorkflowExecutor();
      executor.Execute(xomlFile);

      return ActivityExecutionStatus.Closed;
    }
  }
}

Now this still isn't perfect as a general purpose workflow invoker. Ideally the invoker activity wouldn't wait for the child workflow to complete before returning from Execute. It should start the workflow, return with an Executing status, wait for the workflow to complete, then close itself. But this will do for me so that improvement is left as an exercise for the reader.