XMI: Using C# to Capture UML Models

n my last article, I introduced XMI, an incredibly useful XML standard for interchanging models. Because XMI uses XML and XML is just text, using a schema or standard, it is perfectly suited for storing the model information in a database. As companies start managing IT as portfolios of applications they will need to use these models as a method for determining dependencies and managing their hardware assets in a graphical fashion.

There are a couple of different methods one can employ for capturing the XML model data and putting it into a data store, including, as you might expect, using XSLT. But I’m going to take a different approach, using the C# language. XSLT is a good choice for changing XML files but for more extensive applications that do more than transform data, C# or another high-level language like Java offer more flexibility.

In this article I will take the same ideas and samples from my earlier XMI article, and show how a UML deployment diagram can be dissected using XMI and C#. I’ll begin with a simple deployment diagram of the servers within a fictional car leasing company and use C# to capture some data. This data can easily be added to a database (it is a relatively easy set of ADO calls) or as part of a larger asset based management system. With each example, I’ll add complexity to the original diagram and the information being grabbed.

Example 1: Looking for Server Names

Figure 1. Five Servers: The nodes in this diagram represent the servers from the car leasing company. Each node is a unique server name.

The first thing I want to do is to capture the names of all the servers from the car leasing company. There are five servers, which are represented as a UML deployment diagram (see Figure 1). In the last article I showed how a deployment diagram can be represented as an XML document. To start the process the diagram is created and then exported to an XMI file (Enterprise Architect) or unzipped (MagicDraw). (For this article, again, I am using Enterprise Architect by SparxSystems.com.)

Note: In the last article I mentioned that Enterprise Architect stored each diagram as a separate XMI file but in fact it is by the project.

I will first develop a simple main program that prompts for the name of an XML document representing a valid XMI document. Once the file name is entered it is passed to an object that parses the XMI document and prints out the server names.

Figure 2. Output for Example 1: Each line in the output contains the server name followed by “->” and the XMI element id.

using System;using System.Text;namespace XMI_1{          public class ConsoleUtils          {                    public static string ReadString(string msg)                    {                              Console.Write(msg);                              return System.Console.ReadLine();                    }                    public static void WriteString(string msg)                    {                              System.Console.WriteLine(msg);                    }                    public static void Main(string[] args)                    {                              string name = ReadString("Please enter the XMI filename : ");                              NodeParse np = new NodeParse(name);                              System.Console.ReadLine();                    }          }}

The next step is to parse the XMI document. To do this I have to create an instance of an XMLTextReader and loop until there are no more nodes. XMLNodeType.Element checks every XML element and, in the case where the element is a node, prints it out to the console. The element name is qualified as “UML:Node” however by using _readXMI.LocalName only the “Node” part of the element name is read. See Listing 1a for this portion of the code; the output is shown in Figure 2. (See Listing 1b for the complete source code for Example 1.)

Example 2: Adding Stereotypes
The second example refactors the first example to capture the server names and the “pc server” stereotype, which is the UML method for determining that each node is a server. Figure 3 gives the deployment diagram of the servers with the stereotypes added.

Figure 3. Stereotypes Added: Each stereotype represents the type of node whether it is a PC server, PC client or some other designation for describing the node.

My first step in modifying the prior code to recognize the new stereotypes is to manipulate the AddNode() method.

private void AddNode(XmlTextReader p_readXMI){string nodeID;string nodeName;string stereotypeName;nodeID = p_readXMI.GetAttribute("name");nodeName = p_readXMI.GetAttribute("xmi.id");Console.Write(nodeID);Console.Write(" -> ");Console.WriteLine(nodeName);while (p_readXMI.Read() && (p_readXMI.NodeType == XmlNodeType.Element ||                                       p_readXMI.NodeType == XmlNodeType.Whitespace)){    switch (p_readXMI.LocalName)    {          case "ModelElement.stereotype":          while (p_readXMI.Read() && (p_readXMI.NodeType == XmlNodeType.Element ||                                                 p_readXMI.NodeType == XmlNodeType.Whitespace))          {             if (p_readXMI.LocalName == "Stereotype")                {                    stereotypeName = p_readXMI.GetAttribute("name");                    Console.WriteLine("Stereotype = " + stereotypeName);                 }          }           break;      }    }}

Of particular note is an idiom that I used to search for XML elements that are inside other elements. I added code to check for either elements or white spaces (in XML documents these are spaces) to the p_readXMI.Read loop. This idiom only works so long as each element node is handled at the proper level of encapsulation in the XML document.

Encapsulation of elements is not handled very well in this way. For example, using the idiom code with the following UML will produce undesirable results.

                               
Figure 4. Output for Example 2: Each stereotype is listed after the node name/id that was in Example 1.

The first element is processed as a node, the second element is ignored and breaks the original while loop, and the third element is processed as if it was a normal node at the same level as the first node. To fix this problem, check for the ending tags of elements using XMLNodetype.Endelement. For the purposes of this article, this isn’t a problem and saves the extra coding steps required in checking for ending elements.

Stereotypes in XMI are encapsulated within the Node element. The code for this example checks for an element with the name “ModelElement.Stereotype” and, using the same idiom that I just discussed, processes an encapsulated element of “stereotype” and prints it out to the screen (see Figure 4). (Listing 2 has the complete source code for Example 2.)

Example 3: Adding Hardware Information
Capturing the server names and their stereotypes is not all that useful from an asset management point of view. On the other hand, if your diagrams include information describing the hardware being used this could be very useful: for example, having all of your hardware saved in a database based on UML deployment diagrams would let you track servers, clients, and how they relate to each other .

In UML the hardware node has “tags” that identify the hardware’s properties. Each tag is created within the modeling tool (Enterprise Architect in this case) and is defined by the modeler (or in some cases saved as a UML profile). In the example I have tags for CPU, disk size, memory size, purposes, notes, and Vendor (see Figure 5). Each tag has a value that is assigned either as a basic type (string, integer …) or picked from a list of available values. It is very important to be consistent with the values used. If you use “G HT” in CPU to represent “gigahertz hyper-threaded” then you must use the same convention for every CPU tag.

Figure 5. Adding Hardware To the server/stereotype diagram from Example 2, I’ve added tags that describe each of the node’s hardware. I added a client to the example “Mary Machine” to show that there is also a “pc client” stereotype.

The new, refactored code is the same as before but with some additions for reading tags. The XMI doesn’t encapsulate the tags within the nodes as one would expect. Instead, each attribute is a “TaggedValue” element that references the attribute’s node identifier (XML.id) using the “modelElement” attribute. The difficulty in doing this is that the node elements have to be read before the tag elements (I’ll explain why in a minute) and each node element has to be saved for the tag elements to be attached to them.

In XML there are two ways of reading documents. The first way is to read the entire document and save it in memory as a tree where each element is part of a hierarchy built from the root element. This is the DOM model and is the preferred method for smaller documents. The second method is where the document is read and parsed one element at a time. SAX is an example of this method and is referred to as the push model because it parses the document and sends it back (pushes it) without being prompted.

The other way is using Microsoft’s XMLReaders (XMLTextReader is derived from it), which is the pull model because the program controls when the next element is parsed. The XML document that I use here is small but some of the files that I have parsed are over 500,000 lines of text, which leads me to the XMLReader method. One of the disadvantages of this method is the requirement that the nodes occur before the tags elements.

To save the nodes I need to use a key/value container. The easiest one to use is the Hashtable. In refactoring the main program (see below) I used the enumerations capability that I get from the Hashtable and the IDictionary enumerator to print out the server nodes.

public static void Main(string[] args){Hashtable mainHash;IDictionaryEnumerator ienum;Node tNode;string name = ReadString("Please enter the XMI filename : ");NodeParse np = new NodeParse(name);mainHash = np.getNodes();ienum = mainHash.GetEnumerator();while (ienum.MoveNext())          {                    tNode = (Node)ienum.Value;               System.Console.WriteLine("Node = " + tNode.name + " CPU = " + tNode.CPU);          }          System.Console.ReadLine();          }        }}

I refactored the nodeParse object to check for the element “TaggedValue” and call AddAttributeNode which finds the correct Node in the hashtable and adds the tag through a case statement to the Node. The NodeParse class is shown in Listing 3a.

The Node class is nothing more than an object for holding state. Each tag that I am interested in has its own property. Note that the constructor for Node requires that the object is initialized with the server name.

using System;using System.Text;namespace XMI_1{          class Node           {                    string _name,  _id = "",  _CPU, _MemorySize, _DiskSize,  _Note,                              _Purpose,  _Stereotype,Vendor;                    public Node(string p_id)          {this._name = p_id;}                    public string id                    {get  {          return _id;}                    public string name                    {get   {          return _name;}                      set  {_name = value;}}                    public string CPU                    {get   { return _CPU;}                      set   {_CPU = value;}}                    public string MemorySize                    {get  {return _MemorySize;}                      set  {_MemorySize = value;}}                    public string DiskSize                    {get  {return _DiskSize;}                           set {_DiskSize = value;}          }                    public string Notes                    {get {return _Notes;}                               set {_Notes = value;}}                    public string Purpose                    {get {return _Purpose;}                          set  {_Purpose = value;}}                    public string Stereotype                    {get {return _Stereotype;}                               set  {_Stereotype = value;}}                    public string Vendor                    {get  {return _Vendor;}                        set  {_Vendor = value;}}          }}

I did not provide the output for Example 3, as it is nearly identical to Example 4. (Listing 3b has the complete source code for Example 3.)

Example 4: Finishing with a Little Inheritance
In the final example I inherit Node instances from a Node. Before we were only looking at one node at a time and iterating through them. Node instances represent the relationship between UML nodes and hardware. Nodes provide us with a way to abstract out the common elements of Node instances.

There are two different approaches to this. The first approach is to use the Nodes to represent a common hardware platform. Imagine that I work for a company that wants to order the same computer configuration for all their servers (how strange would that be!). A Node could be used to represent the typical computer configuration, which would then have multiple Node instances that inherit from it. This would save quite a bit of typing! The other approach is when software architects inform infrastructure on how components are distributed. The node represents the different tiers with logical names for each tier. How the tiers map out to the actual hardware is determined by the node instances and is created by the infrastructure group.

In the example (see Figure 6), I show a generic node, “Fleet Management,” which would normally have components that are added to it. Fleet Management was defined by the software architects as a separate tier in a much larger system. To enhance this I might use the “Purpose” tag as well. I have two node instances, “Trucks” and “Persons,” that were defined by the infrastructure team to break the tier into two servers. Since “Trucks” and “Persons” are entirely different domains it is safe to say the architects were involved to split them up. The server names also have “:Fleet Management” attached to them indicating that they are implementing the Fleet Management node.

Figure 6. Trucks and Persons: The Trucks and Persons Node Instances both inherit from the Fleet Management Node. In this example there are no tags to inherit from but there may be other relationships on other diagrams that would carry over to the node instances.

I’ve refactored the main program one more time to print out all the tags, including tags for the node from which each node instance is derived (tNode.classname) and the node’s ID (tNode.classifier).

public static void Main(string[] args){     Hashtable mainHash;     IDictionaryEnumerator ienum;     Node tNode;     string name = ReadString("Please enter the XMI filename : ");     NodeParse np = new NodeParse(name);     mainHash = np.getNodes();     ienum = mainHash.GetEnumerator();     while (ienum.MoveNext())          {          tNode = (Node)ienum.Value;          System.Console.WriteLine("Node = " + tNode.name);                    //Add "if (tNode. != null)" for each Writeline()                    System.Console.WriteLine(" CPU = " + tNode.CPU);          System.Console.WriteLine(" DiskSize = " + tNode.DiskSize);          System.Console.WriteLine(" MemorySize = " + tNode.MemorySize);          System.Console.WriteLine(" Purpose = " + tNode.Purpose);          System.Console.WriteLine(" Notes = " + tNode.Notes);          System.Console.WriteLine("Node refers to = " + tNode.classname);          System.Console.WriteLine("Node addr is " + tNode.classifier);          System.Console.WriteLine();           }         System.Console.ReadLine();       }    }}
Figure 7. Trucks and Persons: In the final output for Example 4, Trucks and Persons each has a node that they refer to and the unique ID for that node.

The classname and the classifier identify which node the node instance is inheriting from. In XMI this is represented like a stereotype as an element encapsulated in a node element.

Now I’ll use a switch statement to check for “ModelElement.taggedValue” instead of “ModelElement.Stereotype,” look for “TaggedValue” elements (there are separate ones for classname and classifier), and add them to the Node object. Again I’ve used the parsing idiom discussed earlier. This code is found in Listing 4a.

Figure 7 shows the final output for Example 4. (Listing 4b has the complete source code for Example 4.)

In this article I covered the way that hardware assets can be read from a diagram. I covered reading the names, the stereotypes, the attributes and reading the relationships between nodes and node instances (inheritance). Even though I did not give explicit code for putting this information into a database I will cover those techniques later, when there is more to add into the database.

In my next article I’ll show nodes being connected and how components and their dependencies can be seen in code.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts