n this article you’ll see an exciting use for XMI?reading the connections between two server nodes in a UML diagram using C#. While my last XMI article collected information about the hardware in a system from XMI, capturing the connections can help answer questions about how servers depend on each other, letting you see how one server breaking down might affect other servers.
Most complex systems contain client, standalone, and server machines. The client machines are modeled as inventory. They have no impact on the system as a whole if they suddenly stop working. Standalone machines are treated in the same fashion; they might have business value, but as far as the overall system is concerned they are isolated. Server machines are different. A server machine is only as useful its connections. In an n-tier environment servers will either be application servers, databases servers, middleware servers, or Web servers.
Modeling such servers requires capturing not only their names and functions but also the links that occur between them. The following examples show how two servers are linked directly through an association or through another server.
A Direct Link Between Two Servers
The example in Figure 1 shows two servers that are linked together. In physical terms a link is the wire connection between two “boxes.” A wire connection is the physical media that transports information from one server to another through protocols including TCP/IP for PC-based servers and SNA for mainframe servers. Logically, a link represents a two-way dependency between servers. If a link is broken in a distributed environment then there are consequences for the whole system.
Dependencies at the link level are the same as each server being able to send data to the other server. If one server cannot send data then that server is no longer being used by the network. Any server that uses the data from that server cannot function either (at least for that instant).
![]() | |
Figure 1. Two Directly Linked Servers: The figure shows two servers for HR (human resources) and CRM (customer relationship management) connected through a link. |
Although Figure 1 doesn’t specify a protocol, in PC environments the default protocol is TCP/IP. From a logical perspective, if the HR server breaks down then the CRM server will not operate correctly. In this case, for example, employees in the HR system could not be assigned to customers in the CRM system. If the CRM server fails then employees’ productivity cannot be measured against the client relationship.
Using C# you can read this relationship and store it in a database (as described in the previous article) or use it to query dependencies. That article also developed some code for reading nodes. This article takes the same code and tweaks it to output the links and the nodes.
In XMI links are represented as UML associations (at least in the Enterprise Architect tool) through the Association element. Here’s some code that recognizes the association.
switch (_readXMI.LocalName) { case "Node": AddNode(_readXMI); break; case "TaggedValue": AddAttributetoNode(_readXMI); break; case "Association": AddAssociation(_readXMI); break; }
Notice that the association is at the same level as a Node. The code reading this data treats the association like any other XML node; each has its own unique identifier. To add associations you need a hash table to persist them in the same fashion as the nodes from the previous article. Listing 1 shows the code to read the association and add it to a hash table.
The code in Listing 1 reads XML nodes and elements using the same pattern as the last article. As I mentioned then, the problem with using this pattern is that it doesn’t work in cases where the XML contains elements you don’t want to read. One solution is to check for the ending tags of elements using XMLNodetype.Endelement. While this will work, it is tedious to write. Another solution is to use the pattern for reading XML at every level of the XML even when the data isn’t pertinent. This acts as a placeholder to fix the problem.
![]() | |
Figure 2. Reading an XMI Association: Each association endpoint (end1 and end2) contains the ID of the nodes, CRM and HR, as shown in the last two lines. |
The example in Listing 1 reads the tags unique to Enterprise Architect in the XMI file, including the style, direction, line color, etc. tags that describe a mixture of display characteristics and other UML properties. The direction tag is of particular note. In this case it contains the value “Unspecified,” which by default is the same as bi-directional (although as you’ll see later, links specified as bidirectional include arrows on both ends of the link).
As shown below, each association contains a tag for the association connection that encapsulates the two association ends. Each end has an isNavigable attribute corresponding to the direction tag mentioned before. In this case both ends are navigable indicating that the link is bidirectional. Each end also contains a type attribute whose value refers to the node to which the end is connected.
Figure 2 shows the output of an example that demonstrates reading an association from XMI. It prints out the association with its endpoints, then prints out the node names and their IDs to show that they match the association’s endpoints.
The Pipes and Filters Pattern
The example you’ve seen does a good job of identifying nodes and associations. But suppose what you really want to replace the link ID values in the association with the association names. This is an easy but still interesting conversion problem with two steps. The first step is to get the hash tables for the nodes and associations, while the second is to use them as parameters to the matching objects. Here’s the code:
NodeParse np = new NodeParse(name);mainNode = np.getNodes();mainAssoc = np.getAssociations();MatchAssociationNodes matchAN = new MatchAssociationNodes(mainNode, mainAssoc);mainMatch = matchAN.getMatched();mEnum = mainMatch.GetEnumerator();
![]() | |
Figure 3. Output from the Pipe and Filters Example: Note that in this version, the “Association First” and “Association Second” lines contain the respective server names, “HR” and “CRM,” rather than the IDs. |
The code for doing this doesn’t seem too unreasonable. I built a NodeParse object to read the nodes and associations and pass the two returned hash tables to a MatchAssociationNodes object which matches the names of the nodes with the association ends. It is a bit messy, what with calling all the hash tables, and moving them between two different objects?but it works. I could encapsulate the matching routine within NodeParse and return the associations with the normalized names. The problem with refactoring in this fashion is that it is very easy to create a “God” class. The “God” class is a classic anti-pattern that occurs when you continually add code with different functionality to the same class. One way to encapsulate the matching class and avoid the “God” anti-pattern is to use the pipes and filters pattern.
In the command line driven world of Unix it was common to write small programs that would filter and modify data. You could combine these to form more complex applications using the “pipe” character (|). This was the origin of the pipes and filters pattern. Here’s an example that uses the technique to match server names to the association ends.
ls -l ~ |grep -v "^d" |awk '{print $5, $8, $3, $6, $7}' |sort -nr |awk '{print $2 " " $3 " " $4, $5 " " $1}'
![]() | |
Figure 4. Transitive Links: In this figure, the HR Server and the CRM Server are separated by the middleware server. |
The preceding example prints a directory (ls -l ~), pipes the output to a grep command to remove the directories, and pipes the result to an awk command to print a few fields. Then it pipes the fields to a sort command, and finally, pipes the sorted output to another awk command that prints out the sorted list. (For more about this technique, see this article.)
The refactored code using a variation of the Pipes and Filters pattern is cleaner, as you can see in the code below and in Figure 3. Notice that I had to declare the NodeParse object np before using it in the get matched method. I can fix this problem by adding a static method to NodeParse. Unfortunately, that would complicate the class.
NodeParse np = new NodeParse(name);mainMatch = MatchAssociationNodes.getMatched(np);IDictionaryEnumerator matchEnum = mainMatch.GetEnumerator();while (matchEnum.MoveNext()){ tAssociation = (Association)matchEnum.Value; System.Console.WriteLine("Association First= " + tAssociation.end1 + " : " + tAssociation.name1ID); System.Console.WriteLine("Association Second= " + tAssociation.end2 + " : " + tAssociation.name2ID);}
Two Servers Linked Through Another Server
![]() | |
Figure 5. Output after Searching for Dependencies: The output clearly shows that the CRM server depends on the middleware server that depends on the HR server. |
You can extend the example to support transitive links. A transitive link is one where there an intermediate server has links between two different servers. In the beginning of the article I mentioned that we could check for dependencies using deployment diagrams. Some dependencies are hidden behind another server, as shown in Figure 4.
Refactoring the same code snippet that used before, it’s now clear that the Pipes and Filters pattern is less complicated then encapsulating the new functionality into the MatchAssociationNode class, or passing the hash tables to other functions in the main program. Here’s the revised code:
NodeParse np = new NodeParse(name);mainMatch = NodeList.getNodeList( MatchAssociationNodes.getMatched(np));IDictionaryEnumerator matchEnum = mainMatch.GetEnumerator();while (matchEnum.MoveNext()){ System.Console.WriteLine((string)matchEnum.Key); System.Console.WriteLine((string)matchEnum.Value);}
Figure 5 shows the output.
![]() | |
Figure 6. Servers Connected in a More Complicated Topology: Notice that the middleware server has three connections. |
In searching for dependencies the code matches the second node of the first link with the first node of the second link. As long as the servers are in a single tier (having one server connect to another server with each server having a maximum of two connections) that code will find all the dependencies.
But what happens with a more complicated topology, where some of the servers have more than two connections, such as that shown in Figure 6?
When applied to this model, the code no longer finds every connection. The WebInterface server is connected to the Middleware server but is also transitively connected to the HR and CRM servers. But the code finds only the WebInterface connection to the CRM server, as shown in Figure 7. The links should also be bidirectional but that is simple to refactor.
Even though the code in this article doesn’t find every possible transitive link and display bidirectionality, if you need those capabilities, you should be able to build on the code shown to meet your specific needs for reading XMI. For example, you may find collecting the node and association information useful when computing the shortest path from one server to another, determining critical points in the enterprise, and calculating performance numbers.
![]() | |
Figure 7. Output from a More Complicated Server Topology. Note that all the servers are represented, but that the Web interface to middleware to HR dependency is missing from this list, as are bidirectional links. |
This article showed how to use C# to pull out dependency information from a UML deployment diagram. This works well enough, but becomes complicated when there are transitive dependencies to consider. You also saw how to use the Pipes and Filters pattern to reduce the complications when adding functionality to a program. The next article in this series will discuss components and how they relate to nodes.