Thursday, November 05, 2009

SAP MII - Overview and Overuse

MII stands for Manufacturing Integration and Intelligence.

It's an offering from SAP which I have been mucking around with for the last few years. Its market is generally mining / manufacturing companies that have a need for agile data aggregation and / or reporting.

It was originally developed on the Microsoft .NET platform by a company in the US and it was called Lighthammer. Lighthammer generated quite a lot of hype amongst these sectors and were subsequently acquired by SAP who were looking for greater market share in the manufacturing space.
SAP re branded the product MII.

I travelled to Sydney in 2006 to sit some MII training, the first course of its kind in Australia. SAP had to find some internal resources who knew enough to conduct the training, and got them a ticket to Sydney (I think the agenda and materials may have been composed on the plane BTW).
This was version 11.5, the current release is 12.1.

There are 2 main aspects of MII:

Visualisation / Reporting (Illuminator)
Here you are basically embedding java applets and vector graphics into html pages, this is geared towards the business user who wants to see enterprise wide information presented appropriately in one place. So, they log into the xMII portal and have all of their relevant reports configured as links.

The technical explanation is custom HTML pages (called irpt's), which have a bunch of JavaScript underneath which call MII server components. These components will paint graphs / images in the browser with all the relevant data binding.
There is also quite a nice javascript object model available to the developer, where features such as asynchronous javascript and XML (read remote scripting), come as standard.

Integration / Intelligence (Xacute)
This part of the product is what I have been focused on.
It is all about getting data from various systems, translating, aggregating, etc the data as required and either passing it off elsewhere, performing business logic etc. anything.

It is like a 5GL programming language, all drag and drop. You have a design surface and you create what is called a transaction, then you can schedule this transaction to run at intervals or expose it as a web service for 3 rd parties / other applications to invoke.

Upon first impressions, Wow, really powerful and easy.
Upon reflection, after having delivered 3 years of business process automation, data interfacing, publish / subscribe messaging layers... Quite limiting.
More of the effort is spent writing tactical workarounds than it is dragging and dropping your way to the next deadline.


All in all, it's a very attractive product to large manufacturing companies as it helps them get to a pseudo service oriented architecture, helps put an end to point to point interfacing nightmares, abstracts the technicalities of proprietary and legacy endpoints and enables a service layer to promote the single source of the truth nirvana.

All experiences based on version 11.5

WIKI

MII Forum

Wednesday, November 04, 2009

Arisen from Blogging Paralysis...

Wow, over three and a half years since.
Slight gear change here, from the technical depths of C# to a more heterogeneous view on software.

Stay tuned...

Monday, April 24, 2006

Embedded Assembly's

Wouldn’t it be nice if all those required DLL’s could just be embedded inside the exe and referenced dynamically?
Turns out, it’s a pretty easy task. Just takes a bit of reflection

The first step is to handle the ‘AssemblyResolve’ event, this way you can help the AppDomain resolve the correct assembly prior to looking for any external assemblies. This has to occur in a static constructor.

static Program()
{
  AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}


Now you can add the DLL’s required to the project (not as references, but as files), then set the ‘Build Action’ to ‘Embedded Resource’.

OK, so once this above event fires, we need to get an array of embedded resource names, loop through them, do a little reflection, then load the requested type =]

string[] resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();

foreach (string resource in resources)
{
  string baseName = resource.Substring(0, resource.LastIndexOf('.'));
  ResourceManager resourceManager = new ResourceManager(baseName, Assembly.GetExecutingAssembly());


We then need to grab the enumerator of ResourceManager to help determine if we are loading an assembly as opposed to an image etc. We assume a byte[] is what we are looking for.

ResourceSet resourceSet = resourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true);
IDictionaryEnumerator enumerator = resourceSet.GetEnumerator();

while (enumerator.MoveNext())
{
  object obj = enumerator.Value;
  if (obj is byte[])
  {
    try
    {
      Assembly assembly = Assembly.Load((byte[])obj);


From here, we can do the final test to make sure we have the requested assembly.

if (args.Name == assembly.GetName().FullName) return assembly;

The beauty is, if we don’t resolve it manually and return a null, the AppDomain will continue probing and it may be resolved somewhere else, or throw an exception as a last resort.

Here’s the full source code

static Program()
{
  AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}

static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
  string[] resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();
  foreach (string resource in resources)
  {
    string baseName = resource.Substring(0, resource.LastIndexOf('.'));
    ResourceManager resourceManager = new ResourceManager(baseName, Assembly.GetExecutingAssembly());
    ResourceSet resourceSet = resourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true);
    IDictionaryEnumerator enumerator = resourceSet.GetEnumerator();
    while (enumerator.MoveNext())
    {
      object obj = enumerator.Value;
      if (obj is byte[])
      {
        try
        {
            Assembly assembly = Assembly.Load((byte[])obj);
            if (args.Name == assembly.GetName().FullName) return assembly;
        }
        catch { }
      }
    }
  }
  return null;
}

Tuesday, March 28, 2006

Invoke Custom NAnt Tasks with CruiseControl.NET

Ever wondered how to hook up a custom NAnt task to continuous integration?
Turns out it’s very simple to do, below is my ‘Hello Worldified’ example.

Once the below class is compiled, place it somewhere on your build server.

using System;
using NAnt.Core;
using NAnt.Core.Attributes;

[TaskName ("MyTestTask")]
public class MyTestTask : Task
{
  private string myParameter;

  [TaskAttribute ("myParameter", Required = true)] // Mandatory
  [StringValidator (AllowEmpty = false)] // Validate
  public string MyParameter
  {
    get
    {
      return myParameter;
    }
    set
    {
      myParameter = value;
    }
  }

  protected override void ExecuteTask()
  {
    // Determine build success, show our property...
    Project.Log(Level.Error, MyParameter); // Will fail the build
    Project.Log(Level.Info, MyParameter); // Will log in NAnt Output
    Project.Log(Level.Warning, MyParameter); // Will display warning
  }
}


Make the following changes to your project’s NAnt build file.
This will force NAnt to get a local copy of the DLL, load it into memory and init its attributes…

<copy file="\\MyServer\MyLibraries\MyTestTask.dll" todir="."/>
<loadtasks assembly="MyTestTask.dll"/>
<target name="run-MyTestTask">
  <MyTestTask myParameter="Hello World" />
</target>


Finally, register the new task in your ccnet.config file.

<tasks>
  <nant>
    <targetList>
      <target>run-MyTestTask</target>
    </targetList>
  </nant>
</tasks>


All that is left now, is to change the task so it does something useful =]

Friday, March 24, 2006

C# Partial Function Application

Sriram Krishnan wrote a great article which really blew my mind.
After reading it 327 times, I was left with one question...

public class Lambda
{
  public delegate T Function<T,T1>(params T1[] args);

  public static Function<K,K1> Curry<K,K1>(Function<K,K1> func, params K1[] curriedArgs)
  {
    return delegate(K1[] funcArgs)
    {
      // Create a final array of parameters
      K1[] actualArgs = new K1[funcArgs.Length + curriedArgs.Length];
      curriedArgs.CopyTo(actualArgs, 0);
      funcArgs.CopyTo(actualArgs, curriedArgs.Length );

      // Call the function with our final array of parameters
      return func( actualArgs );
    };
  }
}

public static int Add (params int[] numbers)
{
  int result =0;
  foreach (int i in numbers)
  {
    result += i;
  }
  return result;
}

// Test call...
Lambda.Function<int,int> adder=Lambda.Curry<int,int>(Add,7);
adder(5, 6); // Returns 18


How is the scope of curriedArgs managed?

So, after a bit of research it turns out that the captured variables of anonymous methods are member variables of the class. Hence it stays in scope (in this case with a value of 7), between the anonymous delegate being created and invoked.

This may be cool, but may also lead to problems like this.

None the less, I'm impressed and given the direction of C# in 2.0 it looks like developers are soon going to have to open their eyes to
Functional Programming or perhaps leave it to the F# guys =]

Monday, March 06, 2006

Marshaling Complex Structs

So Microsoft has done a great deal to help with interop but sometimes it gets tricky. For instance, last week I was trying to call a C++ function which expected a semi-complex struct as its parameter...

typedef struct
{
  int serial_number;
  struct in_addr ip_address;
  time_t oldestfile[MAXDRDCHANNELS];
  time_t newestfile[MAXDRDCHANNELS];
  int group[MAXDRDCHANNELS];
  char groupname[MAXDRDCHANNELS][NAMELENGTH];
  char commonname[MAXDRDCHANNELS][NAMELENGTH];
} foo;


After crunching away for a while, the solution was...

private struct Foo
{
  int serialNumber;
  int ipAddress;
  int oldestFile0;
  int oldestFile1;
  int oldestFile2;
  int oldestFile3;
  int newestFile0;
  int newestFile1;
  int newestFile2;
  int newestFile3;
  int group0;
  int group1;
  int group2;
  int group3;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char groupName0;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char groupName1;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char groupName2;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char groupName3;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char commonName0;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char commonName1;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char commonName2;
  [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=21)] char commonName3;
}


In this case, I knew the array members of the struct were only ever going to contain 4 elements, so I just had to pass them in as individual variables.

Things got tricky when I was trying to marshal a C++ char array.
You can see by setting the above attribute on the char with an explicit size and type, I got away with it.