JDF Blog

Tools, techniques and advice for real-world JDF integrations

Archive for the ‘FluentJDF’ Category

Fluent JDF Version 0.4.0.31 Released

with one comment

Fluent JDF Version 0.4.0.31 has been released. You can get the latest package on Nuget or download the binaries from the Codeplex page. Release notes are on our documentation site. This release has a couple of bug fixes including a work-around for what looks like a .NET Framework bug that causes problems with schema validation under some circumstances. The release notes include a link out to a good blog article that explains the problem. The major feature in this release is the ability to pass up to five string parameters to custom template replacement formulas. Let’s take a quick look at how that works.

I’m going to use a simple formula we added to our JDF Workflow Foundation (JWF) product as an example. JWF is our server framework for building JDF devices and controllers. It uses Fluent JDF’s template support to produce responses to standard JMF command and queries. Because it supports hosting multiple devices or controllers in a single process and JDF requires unique device ids, device descriptions, JMF URLs and other similar things per device or controller, we needed a way to pass those elements to template replacements. Here’s a fragment of XML we need to generate in a response that includes information about the current device.

<Device DescriptiveName="my device name" DeviceID="my device id" .../>

If you wanted to turn that into a template, you’d change it to look like this:

<Device DescriptiveName="[:deviceName:]" DeviceID="[:deviceId:]" .../>

And perform the replacement something like this:

var ticket = Ticket.CreateFromTemplate(templateFileName).With()
    .NameValue("deviceName", JwfLibrary.Settings.ServerSettings.DeviceSettings["my device id"].Name)
    .NameValue("deviceId", "my device id")
    .Generate();

Pretty simple right? Now imagine that you are writing dozens of message handlers that need the current device name or other data that is looked up based on the device id. Seems kind of tedious to look it up every time and pass it as a name-value. Well, parameterized custom functions solve the problem rather nicely. First, you setup the formula as shown in the next code fragment:

//Could use an anonymous function (Func<string, string>).  This example
//will use a static function declared somewhere in the code
public static string ConfiguredDeviceName(string deviceId) {
    if (deviceId == null) {
        return JwfLibrary.Settings.ServerSettings.DefaultDeviceSettings != null ? JwfLibrary.Settings.ServerSettings.DefaultDeviceSettings.Name : null;
    }
    if (JwfLibrary.Settings.ServerSettings.DeviceSettings.ContainsKey(deviceId)) {
        return JwfLibrary.Settings.ServerSettings.DeviceSettings[deviceId].Name;
    }
    return null;
}

//Here's the code to configure the function above as a 
//global custom template replacement formula
FluentJdfLibrary.Settings.WithTemplateEngineSettings()
    .CustomFormula("configuredDeviceName", deviceId => CustomFormulas.ConfiguredDeviceName(deviceId));

Once you have the configuration in place, you need to change the template to use your custom formula and pass it the parameter:

<Device DescriptiveName="[:deviceName=configuredDeviceName(deviceId):]" DeviceID="[:deviceId:]" .../>

Now the code only needs to pass the device ID as shown below:

var ticket = Ticket.CreateFromTemplate(templateFileName).With()
    .NameValue("deviceId", "my device id")
    .Generate();

Admittedly, we’re not saving a huge amount of code here but we are gaining considerable re-use. The nice thing is the template now takes care of the device name lookup for the device. The code only has to pass what the template can’t know until replacement time. I would not suggest using parameterized custom formulas when only one template will use the replacement. However, when you have something that needs parameters and will be used over and over again, a parameterized custom formula can make your life simpler.

Written by Tom Cabanski

August 23, 2011 at 2:54 pm

Posted in FluentJDF

Fluent JDF 0.3.0.9 Released to NuGet

leave a comment »

This release has a number of minor bug fixes and one major new feature: Fluent JDF can now transmit to file URLs so you can use it to submit jobs to devices that only support hot folders. As usual, I’m going to use LinqPad to demonstrate the functionality.

Let’s take a closer look at the new file transmitter. By default, the entire transmission will be sent as a multipart mime file. For example:

void Main() 
{
  var HotFolder = "file://myMachineName/myShareName";
  var ticket = Ticket.CreateIntent().WithInput().BindingIntent().Ticket;
  var message = Message.Create().AddCommand().SubmitQueueEntry().With().Ticket(ticket).Message;
  var result = message.Transmit(HotFolder + "myFile.mim");
  result.Dump();
}

The code above writes a mime file named myFile.mim to the UNC path \\myMachineName\myShareName.

Unfortunately, there is not a standardized way that applications expect to receive JDF via files. Therefore, Fluent JDF gives you many ways to refine how files get dropped. You can send each part (i.e. JDF, JMF and attachments) independently, you can change the order in which the individual parts get dropped, you can suppress any part and you can even change the references from one part to the other to reflect specific rules the receiving application requires. You do this by configuring a file transmission encoder. For example, the following code configures transmissions to the URL file://myMachineName/myShareName to send individual parts dropping attachments, then JDF and finally JMF:

void Main() 
{
  var HotFolder = "file://myMachineName/myShareName";

  //Configure settings for transmissions to the folder (this can be done once during startup)
  FluentJdfLibrary.Settings.WithEncodingSettings()
    .FileTransmitterEncoder("id", HotFolder);
  
  //Send it (notice the destination name 'myFile' means nothing here.  We just need it
  //to make a valid file URL)
  var result = message.Transmit(HotFolder + "myFile.mim");
  result.Dump();
}

The configuration is global and is setup for each destination folder. You can refine the configuration by setting up rules for each kind of part. The next code sample illustrates many of the more advanced configuration options. It does the following:

  • Drops the attachments in \\myMachineName\myShareName\Attachments first
  • Fixes the references to the attachments in the JDF to be file references based on the local destination path e:\HotFolder\Attachments
  • Drops the JDF in \\myMachineName\myShareName\${JobId} second. ${JobId} gets replaced with the JobID of the job. You can also use ${Root} (refers to the executable folder), ${Guid} (a unique GUID), and ${JobKey} (A string “JDF_” + the ID of the root node)
  • Fixes the references to the JDF in the JMF to be file references based on the local destination path e:\HotFolder\${JobId}
  • Drops the JMF in \\myMachineName\myShareName last
void Main()
{
  var HotFolder = "file://myMachineName/myShareName";

  //set configuration for the URL (can be done once at startup)
  FluentJdfLibrary.Settings.WithEncodingSettings()
    .FileTransmitterEncoder("id", HotFolder)
      .FolderInfo(FolderInfoTypeEnum.Attachment, HotFolder + @"Attachments/", 
 	  @"e:\HotFolder\attachments", 1)
      .FolderInfo(FolderInfoTypeEnum.Jdf, HotFolder + @"${JobId}/", 
          @"e:\HotFolder\${JobId}\", 2)
    .FolderInfo(FolderInfoTypeEnum.Jmf, HotFolder, 
        @"e:\HotFolder\", 3);
		
  //Create a sample ticket
  var ticket = Ticket.CreateIntent()
    .WithInput().BindingIntent()
    .WithInput().RunList()
      .AddNode(Element.LayoutElement)
        .AddNode(Element.FileSpec).With().Attribute("URL", "cid:assetName").Ticket;
				
  var message = Message.Create().AddCommand().SubmitQueueEntry().With().Ticket(ticket).Message;

  //Attach the asset for transmission.  Notice that the ID of the part matches the value in the
  //cid url in the FileSpec\@URL
  message.AddRelatedPart(new TransmissionPart(Path.Combine(FilesFolder, "signs.jpg"), "assetName"));
	
  //Send it (notice the destination name 'myFile' means nothing here.  We just need it
  //to make a valid file URL)
  var result = message.Transmit(HotFolder + "myFile");
  result.Dump();
}

In the very near future you will be able to override the configuration when you call Transmit. There will also be a way to load the configuration from a configuration source like the application configuration file.

File transmission will throw an exception if the URL is invalid, the application does not have permission to create the directory if it does not exist, the application does not have permission to write the file or the file already exists in the destination location. File transmissions do not receive a response so the TransmissionPartCollection in the result will always be empty.

Written by Tom Cabanski

July 24, 2011 at 3:22 pm

Posted in FluentJDF

First Look at Fluent JDF: Authoring JDF from Templates

with one comment

A couple days ago I began a series of posts on getting started with Fluent JDF, a complete opensource .NET library for building JDF client applications. In the first installment I showed you how Fluent JDF can help you build tickets from scratch. In this installment, I am going to show you the JDF-aware templating support built into Fluent JDF. I will once again be using LinqPad to demonstrate code. Read the first post in the series if you are unfamiliar with using LinqPad with Fluent JDF.

The idea is quite simple: Take a JDF ticket that works, turn it into a template and generate new, valid JDF tickets based on it. For example, consider the following intent template for a job that prints and side stitches:

<?xml version="1.0" encoding="UTF-8"?>
<JDF DescriptiveName="Print And Corner Stitch" ID="ID_20100406_092100"
  JobPartID="1" MaxVersion="1.4" Status="Waiting" Type="Product"
  Version="1.4" xmlns="http://www.CIP4.org/JDFSchema_1_1"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:type="Product">
  <ResourceLinkPool>
    <ComponentLink Usage="Output" rRef="OutputComponent" Amount="1000"/>
    <LayoutIntentLink Usage="Input" rRef="ID_20100438"/>
    <ArtDeliveryIntentLink Usage="Input" rRef="ID_20100486"/>
    <MediaIntentLink Usage="Input" rRef="ID_20100516"/>
    <BindingIntentLink Usage="Input" rRef="binding1"/>
  </ResourceLinkPool>
  <ResourcePool>
    <ArtDeliveryIntent Class="Intent" ID="ID_20100486" Status="Available">
      <ArtDelivery ArtDeliveryType="DigitalFile">
        <RunListRef rRef="ID_20100488"/>
      </ArtDelivery>
    </ArtDeliveryIntent>
    <LayoutIntent Class="Intent" ID="ID_20100438" Status="Available">
      <FinishedDimensions DataType="ShapeSpan" Preferred="612 792 0"/>
    </LayoutIntent>
    <MediaIntent Class="Intent" DescriptiveName="US Letter, White"
    ID="ID_20100516" Status="Available">
    </MediaIntent>
    <RunList Class="Parameter" ID="ID_20100488" Status="Available">
      <LayoutElement>
        <FileSpec URL="Staple.pdf"/>
      </LayoutElement>
    </RunList>
   <Component Class="Quantity" ComponentType="FinalProduct" Amount="1000"
      DescriptiveName="Green Sample" ID="OutputComponent" Status="Unavailable"/>
   <BindingIntent Class="Intent" ID="binding1" Status="Available">
    <BindingSide DataType="EnumerationSpan" Actual="Left"/>
    <BindingType DataType="EnumerationSpan" Actual="CornerStitch"/>
   </BindingIntent>
  </ResourcePool>
</JDF>

If you wanted to create a job from this, you’d be tempted to simply load it up and send it out.   However, in JDF terms there are a few things you’d need to do before it was truly valid:

  1. Generate a unique JobId
  2. Change Template=”true” to Template=”false”
  3. Make all the IDs unique
  4. Fix all references to the IDs you change

Not too hard, but kind of a pain if you do it yourself. However, if you are using Fluent JDF, it’s as simple as saving your template to a file and writing one line of code:

  InitializeFluentJdf();
  var ticket = Ticket.CreateFromTemplate(@"e:\downloads\Template.jdf").Generate();
  ticket.Dump();

I’ve highlighted the code used to load the template and create a ticket from it. Like last time, I’m including boilerplate library initialization code and a call to Dump() to output the results in LinqPad. The majority of the remaining examples will leave out the extra code.

Anyway, if you run this sample, you’ll get output like this:

<JDF DescriptiveName="Print And Corner Stitch" ID="TI_6bf35" JobID="J_628a0" Template="false" Status="Waiting" Type="Product" Version="1.4" xmlns="http://www.CIP4.org/JDFSchema_1_1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Product">
  <ResourceLinkPool>
    <ComponentLink Usage="Output" rRef="TI_c37d9" Amount="1000" />
    <LayoutIntentLink Usage="Input" rRef="TI_979cb" />
    <ArtDeliveryIntentLink Usage="Input" rRef="TI_0a2e4" />
    <MediaIntentLink Usage="Input" rRef="TI_73d24" />
    <BindingIntentLink Usage="Input" rRef="TI_28d5c" />
  </ResourceLinkPool>
  <ResourcePool>
    <ArtDeliveryIntent Class="Intent" ID="TI_0a2e4" Status="Available">
      <ArtDelivery ArtDeliveryType="DigitalFile">
        <RunListRef rRef="TI_ce198" />
      </ArtDelivery>
    </ArtDeliveryIntent>
    <LayoutIntent Class="Intent" ID="TI_979cb" Status="Available">
      <FinishedDimensions DataType="ShapeSpan" Preferred="612 792 0" />
    </LayoutIntent>
    <MediaIntent Class="Intent" DescriptiveName="US Letter, White" ID="TI_73d24" Status="Available"></MediaIntent>
    <RunList Class="Parameter" ID="TI_ce198" Status="Available">
      <LayoutElement>
        <FileSpec URL="Staple.pdf" />
      </LayoutElement>
    </RunList>
    <Component Class="Quantity" ComponentType="FinalProduct" Amount="1000" DescriptiveName="Green Sample" ID="TI_c37d9" Status="Unavailable" />
    <BindingIntent Class="Intent" ID="TI_28d5c" Status="Available">
      <BindingSide DataType="EnumerationSpan" Actual="Left" />
      <BindingType DataType="EnumerationSpan" Actual="CornerStitch" />
    </BindingIntent>
  </ResourcePool>
</JDF>

Notice how the job id is set, Template is set to false, new IDs have been generated and all the references are fixed. You now have a valid ticket ready to send into a JDF workflow. The other nice thing is this: If you generate another ticket, it will get it’s own job id and resource ids so it will be valid even if sent into the same workflow.

Often, you’ll want to control the JobID. That’s easy too:

var ticket = Ticket.CreateFromTemplate(@"e:\downloads\Template.jdf").With().JobId("theJobId").Generate();

In real life, you also need to change other attributes. For example, you’d probably need to provide a different PDF with each ticket. You’d do that by changing the URL attribute of the RunList referenced from the ArtDelivery:

<RunList Class="Parameter" ID="TI_ce198" Status="Available">
  <LayoutElement>
    <FileSpec URL="Staple.pdf" />
  </LayoutElement>
</RunList>

Start by making the URL’s value a replacement variable like this:

<RunList Class="Parameter" ID="TI_ce198" Status="Available">
  <LayoutElement>
    <FileSpec URL="[:url:]" />
  </LayoutElement>
</RunList>

Then provide a value in your code:

var ticket = Ticket.CreateFromTemplate(@"e:\downloads\Template.jdf")
  .With().NameValue("url", "http://myServer/file.pdf")
  .Generate();

To produce the RunList you want:

<RunList Class="Parameter" ID="TI_ce198" Status="Available">
  <LayoutElement>
    <FileSpec URL="http://myServer/file.pdf" />
  </LayoutElement>
</RunList>

The values for replacement do not have to be strings. For simple types, like int, float and so on, the ToString() method is called to get the replacement value. In the case of DateTime types, the value is formatted as a legal JDF date time. Consider the following template for a QueueEntryStatus query:

<JMF xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" TimeStamp="[:sentDateTime:]" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <Query ID="QES_f66eb" Type="QueueEntryStatus" xsi:type="QueryQueueEntryStatus" />
</JMF>

This is JMF so you have to use the Message class instead of the Ticket class. Otherwise, the code should look pretty familiar:

InitializeFluentJdf();
var message = Message.CreateFromTemplate(@"e:\downloads\template2.jdf")
  .With().NameValue("sentDateTime", DateTime.UtcNow)
  .Generate();
message.Dump();

Notice that we passed the current date time in UTC format. This gives us output with a UTC date time in correct JDF format. The “Z” at the end means it is a UTC date time:

<JMF xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" TimeStamp="2011-07-22T16:50:26.2245983Z" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <Query ID="TI_fd311" Type="QueueEntryStatus" xsi:type="QueryQueueEntryStatus" />
</JMF>

Local time works right too. For example, this code:

var message = Message.CreateFromTemplate(@"e:\downloads\template2.jdf")
  .With().NameValue("sentDateTime", DateTime.Now)
  .Generate();

Produces the time stamp with a local time offset, which in this case is -05:00:

<JMF xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" TimeStamp="2011-07-22T11:56:58.2320198-05:00" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <Query ID="TI_2a7bf" Type="QueueEntryStatus" xsi:type="QueryQueueEntryStatus" />
</JMF>

You can also use formulas to provide default values. Let’s revise the template to use the now() formula, which provides the current date and time in UTC format:

<JMF xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" TimeStamp="[:sentDateTime=now():]" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <Query ID="QES_f66eb" Type="QueueEntryStatus" xsi:type="QueryQueueEntryStatus" />
</JMF>

If we don’t supply a value for “sentDateTime”, the template engine will automatically supply one. So this code:

var message = Message.CreateFromTemplate(@"e:\downloads\template2.jdf").Generate();

Outputs a ticket with the current date time in the TimeStamp attribute:

<JMF xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" TimeStamp="2011-07-22T17:03:01.7058093Z" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <Query ID="TI_54895" Type="QueueEntryStatus" xsi:type="QueryQueueEntryStatus" />
</JMF>

Defaults can also be constants. For example, let’s make the version attribute a variable with a constant default:

<JMF xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="[:version=1.4:]" TimeStamp="[:sentDateTime=now():]" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <Query ID="QES_96237" Type="QueueEntryStatus" xsi:type="QueryQueueEntryStatus" />
</JMF>

If we do not supply a replacement for version:

var message = Message.CreateFromTemplate(@"e:\downloads\template2.jdf").Generate();

We get the default:

<JMF xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" TimeStamp="2011-07-22T17:07:16.4533800Z" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <Query ID="TI_da0c6" Type="QueueEntryStatus" xsi:type="QueryQueueEntryStatus" />
</JMF>

However, when a replacement value is supplied:

var message = Message.CreateFromTemplate(@"e:\downloads\template2.jdf")
  .With().NameValue("version", "1.3")
  .Generate();

The value supplied is used instead of the default:

<JMF xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.3" TimeStamp="2011-07-22T17:07:16.4533800Z" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <Query ID="TI_da0c6" Type="QueueEntryStatus" xsi:type="QueryQueueEntryStatus" />
</JMF>

There are a number of additional advanced features that I will cover in a future post including:

  • Replacing variables based on public properties of an object
  • IEnumerable/repeating section replacements
  • Custom formulas

In the meanwhile, feel free to ask questions over at our Codeplex project.

Written by Tom Cabanski

July 22, 2011 at 5:33 pm

Posted in FluentJDF

First Look at Fluent JDF

with one comment

We have made quite a bit of progress implementing our vision for a truly fluent .NET library for authoring, navigating and transmitting JDF. Although we are not quite ready for an official release, the API is stable enough and functional enough to use.  The project is up at Codeplex and we’re also releasing stable builds on NuGet. It is open source under the commercial-friendly New BSD license.

Getting Started
The easiest way to get started is to head over to NuGet and download the current Fluent JDF package.   You can use it in a project or via LinqPad.

LinqPad is a cool .NET tool that, among other things, lets you write and execute C# code snippets.   As you will see here, it makes a great JDF tool when you hook it up with Fluent JDF.  If you do decide to use LinqPad, I highly recommend purchasing the Intellisense option for $39.  Make sure to download LinqPad for .NET 4.o because that’s the only framework Fluent JDF supports at the moment.  We are looking at adding support for .NET Framework 3.5 in the near future.

Anyway, once you add the NuGet package to a Visual Studio project, you will end up with a folder named packages/FluentJdf.[version number]/tools in the same directory as your project.  Use LinqPad to open FluentJdfSample.linq from there and press the run button.  You should see something like the following:

LinqPad Screenshot

Figure 1 -- LinqPad after running the basic sample. Click to enlarge.

Authoring JDF
Let’s start building a JDF intent ticket.  Replace the Main() function in the sample with the following code and run it:

void Main()
{
	InitializeFluentJdf();

	var ticket = Ticket.CreateIntent().WithInput().BindingIntent().Ticket;
	ticket.Dump();
}

InitializeFluentJdf() sets up everything the library needs. If you are writing your own application, make sure to run the initialization code only once at the beginning of your program. Dump() is provided by LinqPad to show the results in LinqPad’s lower pane. I’m not going to show the initialization code or the dump call after this, but you’ll want to leave them in place.

Dumping the ticket gives us the following xml:

<JDF Type="Product" xsi:type="Product" ID="R_0e748" Status="Waiting" JobID="J_bde70" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <AuditPool>
    <Created AgentName="Fluent JDF (Debug DB: 0)" AgentVersion="0.1.3.0" Author="Fluent JDF (Debug DB: 0)" TimeStamp="2011-07-20T18:27:53.8916546Z" />
  </AuditPool>
  <ResourcePool>
    <BindingIntent ID="R_c53c4" />
  </ResourcePool>
  <ResourceLinkPool>
    <BindingIntentLink rRef="R_c53c4" Usage="Input" />
  </ResourceLinkPool>
</JDF>

As you can see, Fluent JDF automatically handled quite a few details.  The namespace is set correctly, the Version attribute is set, the root JDF has a unique JobID, the binding intent resource is linked properly and placed in the resource pool and it even created a basic audit event.  Not bad for one short line of code.

Let’s add an output component.  The authoring code now looks like this:

var ticket = Ticket.CreateIntent().WithInput().BindingIntent()
  .WithOutput().Component()
  .Ticket;

And the JDF is pretty much what you’d expect:

<JDF Type="Product" xsi:type="Product" ID="R_3f9cc" Status="Waiting" JobID="J_b3991" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <AuditPool>
    <Created AgentName="Fluent JDF (Debug DB: 0)" AgentVersion="0.1.3.0" Author="Fluent JDF (Debug DB: 0)" TimeStamp="2011-07-20T18:29:18.7515083Z" />
  </AuditPool>
  <ResourcePool>
    <BindingIntent ID="R_7ca3d" />
    <Component ID="R_36136" />
  </ResourcePool>
  <ResourceLinkPool>
    <BindingIntentLink rRef="R_7ca3d" Usage="Input" />
    <ComponentLink rRef="R_36136" Usage="Output" />
  </ResourceLinkPool>
</JDF>

How about setting some attributes on the BindingIntent resource?

var ticket = Ticket.CreateIntent()
  .WithInput().BindingIntent().With().Id("myId").Attribute("BindingLength", "Short")
  .WithOutput().Component()
  .Ticket;

This code gives the BindingIntent node specific values for the Id and BindingLength attributes. By the time we release we’ll have attribute setters for all the key attributes. For now, you’ll have to use the Attribute function as shown here.

The JDF includes the attributes we set. Notice how FluentJdf automatically fixed the rRef in the BindingIntentLink to match the new ID value of the resource.

<JDF Type="Product" xsi:type="Product" ID="R_67f3e" Status="Waiting" JobID="J_4900f" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <AuditPool>
    <Created AgentName="Fluent JDF (Debug DB: 0)" AgentVersion="0.1.3.0" Author="Fluent JDF (Debug DB: 0)" TimeStamp="2011-07-20T18:29:56.8136853Z" />
  </AuditPool>
  <ResourcePool>
    <BindingIntent ID="myId" BindingLength="Short" />
    <Component ID="R_1d2fe" />
  </ResourcePool>
  <ResourceLinkPool>
    <BindingIntentLink rRef="myId" Usage="Input" />
    <ComponentLink rRef="R_1d2fe" Usage="Output" />
  </ResourceLinkPool>
</JDF>

It’s also easy to nest JDF elements. For example, consider the following code:

var ticket = Ticket.CreateIntent()
  .WithInput().BindingIntent().With().Id("myId").Attribute("BindingLength", "Short")
  .WithOutput().Component()
  .AddIntent()
    .WithInput().ArtDeliveryIntent()
  .Ticket;

That puts an intent node with an input ArtDeliveryIntent into the root JDF node:

<JDF Type="Product" xsi:type="Product" ID="R_485ee" Status="Waiting" JobID="J_fab72" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <AuditPool>
    <Created AgentName="Fluent JDF (Debug DB: 0)" AgentVersion="0.1.3.0" Author="Fluent JDF (Debug DB: 0)" TimeStamp="2011-07-20T18:30:22.0811305Z" />
  </AuditPool>
  <ResourcePool>
    <BindingIntent ID="myId" BindingLength="Short" />
    <Component ID="R_d0313" />
  </ResourcePool>
  <ResourceLinkPool>
    <BindingIntentLink rRef="myId" Usage="Input" />
    <ComponentLink rRef="R_d0313" Usage="Output" />
  </ResourceLinkPool>
  <JDF Type="Product" xsi:type="Product" ID="R_f2a94" Status="Waiting" JobPartID="JP_98ab0">
    <ResourcePool>
      <ArtDeliveryIntent ID="R_e1907" />
    </ResourcePool>
    <ResourceLinkPool>
      <ArtDeliveryIntentLink rRef="R_e1907" Usage="Input" />
    </ResourceLinkPool>
  </JDF>
</JDF>

The last thing I want to show you is how to add a sub-element to a resource. For example, let’s put an ArtDelivery inside the ArtDeliveryIntent:

var ticket = Ticket.CreateIntent()
  .WithInput().BindingIntent().With().Id("myId").Attribute("BindingLength", "Short")
  .WithOutput().Component()
  .AddIntent()
    .WithInput().ArtDeliveryIntent().AddNode(Element.ArtDelivery)
  .Ticket;

AddNode lets you add any node you want to another node. We plan to keep this method especially for custom elements. However, we will be adding specific add methods for legal child elements based on the standard. It produces exactly the kind of results you’d expect:

<JDF Type="Product" xsi:type="Product" ID="R_432fc" Status="Waiting" JobID="J_2b6da" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="1.4" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <AuditPool>
    <Created AgentName="Fluent JDF (Debug DB: 0)" AgentVersion="0.1.3.0" Author="Fluent JDF (Debug DB: 0)" TimeStamp="2011-07-20T18:31:08.6927966Z" />
  </AuditPool>
  <ResourcePool>
    <BindingIntent ID="myId" BindingLength="Short" />
    <Component ID="R_4fb0e" />
  </ResourcePool>
  <ResourceLinkPool>
    <BindingIntentLink rRef="myId" Usage="Input" />
    <ComponentLink rRef="R_4fb0e" Usage="Output" />
  </ResourceLinkPool>
  <JDF Type="Product" xsi:type="Product" ID="R_82204" Status="Waiting" JobPartID="JP_c98a4">
    <ResourcePool>
      <ArtDeliveryIntent ID="R_9ec11">
        <ArtDelivery />
      </ArtDeliveryIntent>
    </ResourcePool>
    <ResourceLinkPool>
      <ArtDeliveryIntentLink rRef="R_9ec11" Usage="Input" />
    </ResourceLinkPool>
  </JDF>
</JDF>

Getting Additional Help
We’re working on a set of getting started tutorials and screen casts. We’ll announce them here when they become available. If you have questions or ideas about what you’d like us to cover in the tutorials, post them on our discussion group.

Written by Tom Cabanski

July 20, 2011 at 6:45 pm

Posted in FluentJDF

Imagining a More Fluent JDF for .NET

leave a comment »

Imagine creating a trivial intent ticket with nothing more than a component output.  For example, something like this:

<?xml version="1.0" encoding="utf-8"?>
<JDF Type="Product" p1:type="Product" ID="MyId" JobID="MyJobId" xmlns:p1="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.CIP4.org/JDFSchema_1_1">
  <ResourcePool>
    <Component ID="component1" />
  </ResourcePool>
  <ResourceLinkPool>
    <ComponentLink Usage="Output" rRef="component1" />
  </ResourceLinkPool>
</JDF>

It’s not too hard to do but it isn’t exactly trivial either.  For example, here’s some code using XDocument:

public static class TestAuthoring {
	static readonly XNamespace JdfNamespace = "http://www.CIP4.org/JDFSchema_1_1";
	static readonly XNamespace XsiNamespace = "http://www.w3.org/2001/XMLSchema-instance";

	public static XDocument GetTicket() {
		return new XDocument(
			new XElement(JdfNamespace.GetName("JDF"),
						 new XAttribute("Type", "Product"),
						 new XAttribute(XsiNamespace.GetName("type"), "Product"),
						 new XAttribute("ID", "MyId"),
						 new XAttribute("JobID", "MyJobId"),
						 new XElement(JdfNamespace.GetName("ResourcePool"),
									  new XElement(JdfNamespace.GetName("Component"),
												   new XAttribute("ID", "component1"))),
						 new XElement(JdfNamespace.GetName("ResourceLinkPool"),
									  new XElement(JdfNamespace.GetName("ComponentLink"),
												   new XAttribute("Usage", "Output"),
												   new XAttribute("rRef", "component1")))));
	}
}

I know it’s sort of painful to read even though I took the trouble to pull the name spaces up into variables.  I guess I could have gone further and made things a little better.  How about this?

public static class TestAuthoring {
	static readonly XNamespace XsiNamespace = "http://www.w3.org/2001/XMLSchema-instance";

	public static XDocument GetTicket() {
		return new XDocument(
			new XElement(Element.Jdf,
						 new XAttribute("Type", "Product"),
						 new XAttribute(XsiNamespace.GetName("type"), "Product"),
						 new XAttribute("ID", "MyId"),
						 new XAttribute("JobID", "MyJobId"),
						 new XElement(Element.ResourcePool,
									  new XElement(Element.Component,
												   new XAttribute("ID", "component1"))),
						 new XElement(Element.ResourceLinkPool,
									  new XElement(Element.Component.Link(),
												   new XAttribute("Usage", "Output"),
												   new XAttribute("rRef", "component1")))));
	}
}

public static class Element {
	static readonly XNamespace JdfNamespace = "http://www.CIP4.org/JDFSchema_1_1";

	public static XName Jdf = JdfNamespace.GetName("JDF");
	public static XName ResourcePool = JdfNamespace.GetName("ResourcePool");
	public static XName ResourceLinkPool = JdfNamespace.GetName("ResourceLinkPool");
	public static XName Component = JdfNamespace.GetName("Component");

	public static XName Link(this XName resourceName) {
		return XName.Get(resourceName.LocalName + "Link", resourceName.NamespaceName);
	}
}

The static Element class helps a little but really not all that much.  It’s still a bit of a pain especially when you also have to know what the attributes are, spell them correctly, maintain the references properly an so on.  Wouldn’t it be nice to put all those worries aside and be able to write clean, simple code to produce the JDF?

public static XDocument GetTicket() {
	return Ticket.CreateIntent().WithOutput().Component().Ticket;
}

Well, you will be able to so in the very near future by taking advantage of the FluentJDF library Onpoint On Demand is planning to release as open source in the next few months.  It will include everything you need to build a JDF client that creates JDF, validates it, sends it to JDF consumers using JMF and parses synchronous responses.  All the basic JMF messages will be supported.  We also plan to include extended xpath query and traversal functionality that handles references automatically, knows about process and is just generally smart about JDF structure.  We have not selected a license yet.  However, we are planning to use a commercial-friendly license that will not force you to release source code for your product if you use and distribute FluentJDF.

I will blog more on FluentJDF as we get closer to release.

Written by Tom Cabanski

June 22, 2011 at 3:14 pm

Posted in FluentJDF