Test Drive the design of an ADAM Media handler

Posted by on February 26, 2012 in Software Development | 0 comments

Test Drive the design of an ADAM Media handler

Recently I became a certified ADAM developer. ADAM is a product of ADAM Software that provides Media management. ADAM delivers intelligent Digital Asset Management (DAM) for fast, secure, easy-to-use access, sharing and distribution. This enables centralized automation to ensure data accuracy and fast time-to-market.

Assets or media files are imported into ADAM using a media engine. ADAM offers out of the box a standard media engine for importing different file formats. I have photography as hobby and use a Nikon camera and lenses. The default installation of ADAM offers no possibility to import the RAW Nikon format (.NEF files). Note that ADAM can import all files but it will not generate previews, thumbnails or extract Exit information during import.

ADAM is highly customizable, it includes a comprehensive SDK and documentation. Customization is possible using the Microsoft .NET Framework.  Therefore I decided to develop a custom Media Engine using C# to import Nikon NEF files.

Media Engines

ADAM uses the following pipeline that consists of media engines for importing different types of files. Each media engine is specialized for certain types of files. My new custom media engine (Nikon NEF Media Engine) will be responsible for handling Nikon Raw NEF files.

ADAM Media Engine

For every files that must be imported ADAM creates several media actions. These media actions are handed off to the pipeline of the registered media engines. If a media engine cannot handle the action for the specified file type, the action is handed to the next media engine and so on, until there is a media engine that handles the action for the specified file type.

So if a Create Thumbnail Action is created for the File1.NEF file, Media Engine A and Media Engine B do not handle the action. When the action gets to the Nikon NEF Media Engine the action is handled. The Nikon NEF Media Engine creates a thumbnail of the NEF file and stores it together with the original NEF file in ADAM.

Nikon NEF SDK

Before I could create the media engine I had to get more information about the NEF format itself. Nikon released not so long ago a SDK that includes documentation and some samples on how to read and process NEF files.

The SDK includes a Visual Studio 2008 MFC sample application together with the unmanaged library itself. I decided to create a managed C++ wrapper to wrap the methods so that it was easier to call these methods from a managed Microsoft .Net application, the custom media engine. This was a real pain, the documentation was not that good and I have spent many hours trying to get the actual data from the NEF files. I was eventually able to extract the image data but getting the thumbnails from the image never succeeded.

A note to someone else trying to use the SDK. Before getting the actual image data you have to first set the image info. So before calling GetImageData you need to call SetImageInfo which makes no sense at all. Tried to use the same pattern with GetThumbnailData but did not got very far.

I therefore  left the NIKON SDK and searched for another way to read Nikon NEF files.

LibDCR

To the rescue comes LibDCR, a library to read RAW images from digital cameras. It reads file extension such as: RAW, CRW, NEF, CR2, DNG, ORF, ARW, ERF, 3FR, DCR, X3F, MEF, RAF, MRW, PEF, SR2. LibDCR is a conversion of the well-known dcraw.c software, created by Dave Coffin, into a C library. LibDCR is fully based on dcraw.c and inherits the same features and license terms.

I decided to go the easy and fast route and use the command line tool that is included with LibDCR.

Test Driven Design of a Custom Media Engine

With all prerequisites in place it was time to create the custom media engine. As I am a big fan of Test Driven Design I wanted to find out how easy it was to design a new ADAM media engine using tests. The tools I used were NCrush for continuous testing, NSubstitute for mocking and FluentAssertions for well… describing the assertions of the test in a fluent language. Tests are described using the normal NUnit test attributes.

Before I begin I normally have a basic design sketch in my head, when I started it looked something like this.

Basic Design Media Handler

A custom media engine must derive from the base ADAM Media Engine. My NikonNEFMediaEngine has a dependency on an ActionSelector which is responsible for selecting the correct action for the given file and media action.

First Test

I always try to start easy with the first test. Each custom media engine should implement an Id property that returns a unique string that identifies the media Engine. So my first test looked like this.

[Test]
public void Id_Should_Return_Specific_Id()
{
  var mediaEngine = new NikonMediaEngine();
  mediaEngine.Id.Should().Be(Constants.NikonMediaEngine);
}

Then I implemented the NikonMediaEngine including its Id property and run the test. I did not come that far because the media engine does not have a constructor that take no argument. The base media engine needs the ADAM application object in its constructor. No problem, I added the constructor and used NSubstitute to create a stub of the application object.  So the new first test became something like this.

[Test]
public void Id_Should_Return_Specific_Id()
{
  Application application = Substitute.For<Application>();
  var mediaEngine = new NikonMediaEngine(application);
  mediaEngine.Id.Should().Be(Constants.NikonMediaEngine);
}

But to my surprise this gave the following error: “Adam.Core.UnauthorizedOperationException : You do not have sufficient rights to perform this operation.”. Rights to perform this operation?? Note that I included the dependent ADAM dll’s in this solution and therefore had no external dependencies. I decided to figure out what is happening inside the Media Engine.


protected MediaEngine(Application app)
{
  if (app == null)
    throw new ArgumentNullException("app");
  if (!app.IsLoggedOn)
    throw new UnauthorizedOperationException();
  this.a = app;
}

The reflected source code of the constructor of the base media engine showed the problem, looking at the source code of an external library is as easy as pressing Ctrl-B if you have Resharper installed. In the constructor it checks via the application object the IsLoggedOn property and if not it throws an exception. I don’t like it when constructors throw exceptions especially is it based on the state of another object, like here the application object.

But it gave me a clue on how to work around this problem. I just had to instruct the Application stub to return true for the IsLoggedOn property like this.


[Test]
public void Id_Should_Return_Specific_Id()
{
  Application application = Substitute.For<Application>();
  application.IsLoggedOn.Returns(true);
  var mediaEngine = new NikonMediaEngine(application);
  mediaEngine.Id.Should().Be(Constants.NikonMediaEngine);
}

Too bad that NSubstitute gave the following error “failed: NSubstitute.Exceptions.CouldNotSetReturnException : Could not find a call to return from.”. Which basically means that the IsLoggedOn property is not virtual and not abstract and therefore cannot be overridden by NSubstitute.

Aaaaaargh!!!! just the first test and already hitting a rock regarding testing my custom media engine. Basically now I could do one of the following four things:

  1. Forget testing this class, make it as thin as possible and focus on testing the rest
  2. Integrate and run ADAM locally so that the application object can be logged on
  3. Use a different mocking framework such as TypeMock
  4. Give up and don’t use TDD

I really don’t like it that I have to run ADAM just to be able to run unit test, this will also have an impact on the build infrastructure. TypeMock is a mocking framework that is able to stub non virtual method and static methods but somehow it does not feel right. You are creating a dependency on something internal ADAM thing which may make the tests brittle.

So the only viable option for me is option 1 as given up is not really an option

Making the Nikon NEF media engine class as thin as possible

The only real implementation necessary is in the OnExecute method which your custom media engine must override. This method has an MediaAction argument which indicates which action the pipeline wants your media engine to execute.

public override bool Run(MediaAction action)
{
  return mediaEngineActionSelector.Select(action.Id).Execute(action);
}

I implemented a Select method which select the right action based on the action.id. Next in the run method I call the Execute method on the selected action. This way the untested code can be decreased to a minimum.

When a media engine cannot perform the action indicated by the given action id it should return false. Therefore if the media engine action selector can not find an appropriate action it returns an NullMediaEngineAction which just returns false. This prevents creating if statements all over the code.

/// <summary>
/// Media Engine Action implementing the null object pattern. This prevents
/// additional if statements in the engine.
/// </summary>
public class NullMediaEngineAction : MediaEngineAction
{
  public override bool Execute(MediaAction action)
  {
    return false;
  }

  public override string Id
  {
    get
    {
      return "NullMediaEngineAction";
    }
  }
}

Conclusion

By making the custom media handler class as thin as possible it was possible to implement the media handler using TDD. I hope that ADAM software takes Test Driven Design and Development serious and in the future will make it easier for developers to practice TDD.

Link to the source code will be here as fast as possible.