Skip navigation.
Home

Inside UML, XMI and XQuery

!Overview Designing software gets very complex when trying to meet highly demanding environment requirements. While an architect has always been a key person of a successful development team, new evolving technologies move the potential to other team members like developers and QA teams. Technologies like Model Driven Architecture and Model Driven Development allow all team members to participate in higher level roles and add much more value. In this article we will walk through a sample MDA approach utilizing UML, XMI and XQuery technologies. We will also notice how all team members can participate to achieve such approach. Please note that code listings provided here are for demonstration and proof of concept purposes only. !The Problem In our example, we will try to build an Orchestration system that can be configured dynamically to do a simple job. The job is to pull a message from a business component, pass this message to an interceptor, get the output of this interceptor, and use this output as a message to a receiver business component. Dynamic configuration means that there should be a maestro for this orchestration, and this maestro should be able to orchestrate the system using some kind of configuration data. The following sequence diagram simplifies what needs to be done. |> !The Orchestration Schema To define the configuration contract between the system client and the system, we can build an XML schema. This schema will allow us to define components participating in this orchestration, and provide a way of wiring these components in the terms of a sender, an interceptor and a receiver.\\ The schema allows us to define multiple components and their actual classes, and identifying these components using an arbitrary ID. Wiring provides a way to define a wire, which is a logical connection between two components, one acting as a sender and the other is a receiver. The wire also holds the interceptor information. Instances of this schema will be supplied to the maestro to start the orchestration process as shown in the previous sequence diagram. Sender, receiver and interceptor have an operation attribute that references the method that should be invoked by the maestro. The following code listing is the schema definition: {{{ }}} !The Orchestration UML Profile While very complex systems can be successfully modeled using UML, a more advanced usage for specific requirements of a system is to create a profile. The OMG UML specification states that: "The Profiles package contains mechanisms that allow metaclasses from existing metamodels to be extended to adapt them for different purposes. This includes the ability to tailor the UML metamodel for different platforms (such as J2EE or .NET) or domains (such as real-time or business process modeling)" In our case, it is possible to model an orchestration process using the definitions specific to our system if we have a profile for orchestration. The following diagram shows a profile that can be used later in an orchestration deployment diagram: |> The profile four stereotypes:\\ 1. Addressing type for senders and receivers\\ 2. Linking type for linking senders or receivers to a component\\ 3. Business Component type for defining components\\ 4. Wire type for connecting senders and receivers\\ The next section will make use of this profile to define an orchestration process. !Applying the Profile Before defining our process, we will define three sample components that can be utilized by the process: |> The diagram defines three classes:\\ 1. XmlUtil to act as an XML document provider\\ 2. StreamUtil, an interceptor that can provide an input stream out of an XML document\\ 3. HttpUtil, an HTTP poster that reads an input stream and posts the read data over HTTP protocol\\ Now let's define our orchestration process:\\ |> Using our profile, we defined the following elements:\\ 1. XmlComponent, encapsulates the XML document provider\\ 2. HttpComponent, encapsulates the HTTP poster\\ 3. Sender, defines a sender linked to the document provider\\ 4. Receiver, defines a receiver linked to the HTTP poster\\ 5. A wire between the sender and the receiver, holding the interceptor reference\\ In the next section we will see how we can generate a configuration XML file that confirms to our schema, using this model. !Transformation In this section we will utilize the XMI and XQuery technologies to generate the orchestration process configuration file. After modeling our process as we have seen in the previous section, we need to export the model in XMI format. Since XMI is actually XML, we can use a transformation technology like XSLT or XQuery to generate our configuration file. The following listing is a sample XQuery code. This code works with MagicDraw 10.5, you may need to modify it if you are using some other tool. {{{ xquery version "1.0"; declare namespace xmi="http://schema.omg.org/spec/XMI/2.1"; declare namespace uml="http://schema.omg.org/spec/UML/2.0"; declare namespace egjug="http://www.egjug.org/xml/ns/component"; declare variable $doc as xs:string := "business-component-profile.xml"; declare variable $root as element() := doc($doc)/*; declare variable $profile as element()* := $root//*[@xmi:type="uml:Profile" and @name="org.egjug.uml.business-component"]; declare function egjug:getBusinessComponents() as element()* { for $component in $root//*[@xmi:type='uml:Component'] where exists($component/xmi:Extension/modelExtension/appliedStereotypeInstance) return $component }; declare function egjug:getFullPackageName($id as xs:string) as xs:string* { for $child in $root//*[@xmi:id=$id], $parent in $child/parent::*[@xmi:type='uml:Package'] where (string($child/@owningPackage) eq string($parent/@xmi:id) ) return ( string($parent/@name), egjug:getFullPackageName( string($parent/@xmi:id) ) ) }; declare function egjug:getFullClassName($id as xs:string) as xs:string { let $packageName := string-join(reverse(egjug:getFullPackageName($id)), '.') let $className := string($root//*[@xmi:id=$id]/@name) let $fullNameSeq := ($packageName, $className) return string-join($fullNameSeq, '.') }; declare function egjug:getStereotypeById($id as xs:string) as element()* { $profile//*[@xmi:type='uml:Stereotype' and @xmi:id=$id] }; declare function egjug:getAppliedStereotypeInstance($component as element()) as element()* { $component/xmi:Extension/modelExtension/appliedStereotypeInstance }; declare function egjug:getStereotypeByComponent($component as element()) as element()* { let $stereotypeInstance := egjug:getAppliedStereotypeInstance($component) let $classifier := string($stereotypeInstance/@classifier) return egjug:getStereotypeById($classifier) }; declare function egjug:getAttribute($component as element(), $attName as xs:string) as element()* { let $stereotype := egjug:getStereotypeByComponent($component) let $attNameId := string( $stereotype/ownedAttribute[@xmi:type='uml:Property' and @name=$attName]/@xmi:id) let $appliedStereotypeInstance := egjug:getAppliedStereotypeInstance($component) for $value in $appliedStereotypeInstance/slot[@definingFeature=$attNameId]/value/@value return element {$attName} {string($value)} }; declare function egjug:getStringAttribute($component as element(), $attName as xs:string) as xs:string* { let $stereotype := egjug:getStereotypeByComponent($component) let $attNameId := string( $stereotype/ownedAttribute[@xmi:type='uml:Property' and @name=$attName]/@xmi:id) let $appliedStereotypeInstance := egjug:getAppliedStereotypeInstance($component) for $value in $appliedStereotypeInstance/slot[@definingFeature=$attNameId]/value/@value return string($value) }; declare function egjug:getExtendedAttribute($component as element(), $attName as xs:string) as xs:string { let $stereotype := egjug:getStereotypeByComponent($component) let $attNameId := string( $stereotype/ownedAttribute[@xmi:type='uml:Property' and @name=$attName]/@xmi:id) let $appliedStereotypeInstance := egjug:getAppliedStereotypeInstance($component) let $element := string( $appliedStereotypeInstance/slot[@definingFeature=$attNameId]/ xmi:Extension/modelExtension/value/@element) return $element }; declare function egjug:getWiring() as element()* { for $wire in $root//*[@xmi:type='uml:Manifestation'] where (string(egjug:getStereotypeByComponent($wire)/@name) eq "wire") return $wire }; declare function egjug:getOperation($component as element()) as xs:string { let $operationId := string(egjug:getExtendedAttribute($component, "operation")) return string($root//*[@xmi:type='uml:Operation' and @xmi:id=$operationId]/@name) }; declare function egjug:getLinkedComponentId($artifact as element()) as xs:string* { let $comp := egjug:getLinkedComponent($artifact) return egjug:getStringAttribute($comp, "id") }; declare function egjug:getLinkedComponent($artifact as element()) as element()* { for $mnf in $root//*[@xmi:type='uml:Manifestation'] where (string(egjug:getStereotypeByComponent($mnf)/@name) eq "linking") and (string($mnf/@client) eq string($artifact/@xmi:id)) return $root//*[@xmi:type='uml:Component' and @xmi:id=string($mnf/@supplier)] }; { for $bizcomp in egjug:getBusinessComponents() let $id := egjug:getStringAttribute($bizcomp, "id") let $classId := egjug:getExtendedAttribute($bizcomp, "component-class") let $class := egjug:getFullClassName($classId) return } { { for $wire in egjug:getWiring() let $interceptorId := egjug:getExtendedAttribute($wire, "interceptor") let $interceptor := egjug:getFullClassName($interceptorId) let $supplier := $root//*[@xmi:type='uml:Artifact' and @xmi:id=string($wire/@supplier)] let $client := $root//*[@xmi:type='uml:Artifact' and @xmi:id=string($wire/@client)] return } } }}} Executing this query against a process modeled using our profile, will result into a process orchestration XML file confirming to our schema. The next listing is the query output for the process modeled in the previous section: {{{ }}} !Writing the Maestro Since our system configuration is based on an XML schema, it is possible to generate bindings to our schema using JAXB 2.0. To generate the bindings from command line: {{{ xjc -d src xml\orchestration.xsd }}} Obviously reflection should be used to instantiate classes and invoke methods unknown to the system until runtime. The following listing is a sample implementation of the maestro: {{{ public final class Maestro{ public void orchestrate(File xml){ Source source = getSource(xml); Orchestration orchestration = getOrchestration(source); for(Wire wire : orchestration.getWiring().getWire()) doWire(wire); } private Source getSource(File xml){ Source source = null; try{ source = new SAXSource(new InputSource(new FileReader(xml))); } catch(FileNotFoundException e){ e.printStackTrace(); } return source; } private Orchestration getOrchestration(Source source){ JAXBContext jc; Orchestration orchestration = null; try{ jc = JAXBContext.newInstance("org.egjug.xml.ns.component"); Unmarshaller unmarshaller = jc.createUnmarshaller(); Object obj = unmarshaller.unmarshal(source); JAXBElement element = (JAXBElement) obj; orchestration = (Orchestration) element.getValue(); }catch(JAXBException e){ e.printStackTrace(); } return orchestration; } private void doWire(Wire wire){ try{ Class interceptorClass = Class.forName(wire.getInterceptor()); Object interceptor = interceptorClass.newInstance(); Class senderClass = Class.forName( resolveBusinessComponent(wire.getSender()).getComponentClass()); Object sender = senderClass.newInstance(); Class receiverClass = Class.forName( resolveBusinessComponent(wire.getReceiver()).getComponentClass()); Object receiver = receiverClass.newInstance(); Method senderMethod = pickAddressMethod(senderClass, wire.getSender()); Method receiverMethod = pickAddressMethod(receiverClass, wire.getReceiver()); Method interceptorMethod = pickAddressMethod(interceptorClass, wire.getOperation()); Object senderOutput = senderMethod.invoke(sender, new Object[]{}); Object interceptorOutput = interceptorMethod.invoke(interceptor, senderOutput); receiverMethod.invoke(receiver, interceptorOutput); } catch(ClassNotFoundException e){ e.printStackTrace(); } catch(IllegalAccessException e){ e.printStackTrace(); } catch(InstantiationException e){ e.printStackTrace(); } catch(InvocationTargetException e){ e.printStackTrace(); } } private BusinessComponent resolveBusinessComponent(Address address){ BusinessComponent bizcomp = null; try{ bizcomp = (BusinessComponent) address.getBusinessComponent(); } catch(ClassCastException e){ e.printStackTrace(); } return bizcomp; } private Method pickAddressMethod(Class cls, Address address){ String operation = address.getOperation(); return pickAddressMethod(cls, operation); } private Method pickAddressMethod(Class cls, String operation){ //TODO: add support for method overloading for(Method method : cls.getMethods()) if(method.getName().equals(operation)) return method; return null; } public static void main(String...args){ Maestro maestro = new Maestro(); maestro.orchestrate(new File("xml/orchestration.xml")); } } }}}
AttachmentSize
seq.png37.19 KB
schema.png16.18 KB
profile.png47.64 KB
cls-dgm.png48.08 KB
impl-dgm.png51.87 KB