BT

Case study: A new approach to integrating architectures post-merger at Lawson

Posted by Barry Livingston on May 21, 2007 |

Introduction

In this age of mergers and acquisitions, a common problem faced by solution architects is the integration of product lines in a manner that provides a consistent user experience. When Lawson Software, Inc. (Lawson®) merged with Intentia International AB (Intentia®) in April of 2006, the development staff was faced with the task of creating a seamless, homogeneous user experience capable of improving user productivity, user satisfaction and information integration while spanning the new company's flagship product lines (known as "M3" for product-centric industries that Make, Move and Maintain products or assets and "S3" for service industries that Staff, Source and Serve toward fulfilling customer needs).

This case study will investigate Lawson's approach to the dilemma, as well as taking a detailed technical look at some of the more interesting facets of their solution and overall system architecture.

Problem Domain

Prior to their merger, both Lawson and Intentia provided Enterprise Resource Planning (ERP) solutions to select target markets and geographies. Lawson's main product line, S3, was specifically designed for service-centric industries primarily in the Americas. Helping their customers "staff, source, and serve", S3 applications ran the gamut of ERP functions, including enterprise performance management, business process management, enterprise financial management, supply chain management, services management and human capital management. Although similar in intention, Intentia's M3 products were primarily targeted at shipping and manufacturing companies in EMEA and APAC - any customer who might "make, move, and maintain" goods or equipment. The M3 applications included manufacturing operations, supply chain execution and optimization, customer sales and service, enterprise asset management, financial management and controlling and business process management services. The rationale for the merger of the two corporations is obvious - complementary product lines, geographies and target industries.

While the systems of both companies had their similarities, their implementations differed greatly. Intentia's M3 system was centered around a server-side Java business logic tier, which was accessed by a number of different client applications. Intentia's client user interfaces (UIs) had progressed over the years, starting with a MicroFocus Cobol/C++ rich client, progressing through Active Server Pages (ASP) and JavaServer Pages (JSP) based thin client solutions to an Asynchronous JavaScript and XML (AJAX)/JSP based thin client portal, known as the Workplace Foundation. The Lawson S3 application suite is a mix of Java-based and 4th Generation Language (4GL)-based solutions, but is evolving to pure Java-based in a similar overall fashion. Between the two companies, a number of different client technologies were being utilized, ranging from the Workplace Foundation web portal to a C# based mobile sales rich client.

As a result of the merger, Lawson was seeking a way to create a client application that could leverage both of its flagship product lines. Specifically, they hoped to create a client application that would:

  • Provide a common look and feel for applications accessed through Windows XP and Vista clients, (98% of their customer base were accessing the software from a Windows client.)
  • Provide a common way for users to launch applications and receive messages
  • Provide a well-defined and pluggable way to host new applications and functionality
  • Provide a rich, productive user experience
  • Run from "one code base", keeping business logic on the server-side so it could be shared across different clients.
  • Interact with both Java and non-Java business logic tiers.
  • Allow the use of both previous clients and the newly developed client against the same server instance.

Solution Overview

In order to help ensure that the choice of technologies for the new client application was appropriate, Lawson decided to enlist the assistance of frog design europe gmbh (www.frogdesign.com) to help analyze current and emerging user interface technologies. Together, they concluded that the best solution to the stated requirements was the creation of a rich client application built on Microsoft's new Windows Presentation Foundation (WPF) and certain components of the new Windows Communication Foundation (WCF) - both part of the .Net 3.0 framework. A number of factors contributed to this choice:

  • The launch of the new product would coincide with the Windows Vista Launch, allowing Lawson to be the first amongst its competitors to utilize this technology.
  • Since Windows is the platform of choice for 98% of Lawson's customers, embracing key components of .Net was seen as a way to support their customers' investments
  • Windows Presentation Foundation provides good separation of concerns, allowing graphic design to be separated from other implementation details.
  • WPF provides a uniform platform for combining forms, controls, audio, video, on-screen and fixed-format documents, 2D and 3D Graphics.
  • WPF provides scalable, resolution independent UI rendering
  • The Windows Communications Foundation provides an easily-extensible programming model/framework for communication
  • The WCF nicely decouples the application code from the communication code. Client code using the WCF is the same regardless of where the service is located - across application domains, across processes, on the same machine, across machines, etc.
  • The WCF provides a declarative model for composite applications and service oriented architecture
  • WCF endpoints are configurable after deployment

The Client Application

As can be seen from the above diagram, the new rich-client (aka "Lawson Smart Client™") has 3 significant design layers, plus consideration for supporting user data or preference settings. The first of these, the Presentation Layer, is responsible for user interaction and life-cycle management of the hosted widgets. It is essentially the client "canvas". Widgets from each different application type (M3, S3, etc.) can be added to the canvas, allowing the user to launch tasks and respond to incoming events. In addition, users can alter the look and feel of the client by changing the "skin" of the canvas.

The Render Logic Layer consists of a number of pluggable "engines" responsible for rendering the content of a specific application line. Content is generated at runtime from a combination of metadata, user data, and other predefined resources, ensuring a consistent look and feel.

The Data Access Layer utilizes WPF data binding to tie UI components to business logic or data components. Multiple storage strategies can be utilized in this way, enabling the UI to gather data from user preferences, client configuration, session context, etc. The most common of these strategies is the use of WCF Endpoints that connect to server-side components via Web Services.

Microsoft SQL Server Compact Edition is used to store user preferences, usage statistics, and user state. This user data can be made available to clients if the user sets up a "roaming profile", which allows the replication of user data to a server that can be accessed from any Lawson Smart Client device.

The Server Side

As can be seen in the diagram above, Lawson and Intentia had developed a significant amount of industry-specific, server-side business logic and functionality over the years. In fact, all of the server-side components shown in the diagram existed in some manner prior to the development of the new Smart Client. To protect and extend their customers' investments and skill sets and ease the migration to new technologies, continuing the use of the existing server-side components, both Java and non-Java, was the best option for Lawson.

To facilitate the rapid production of the initial client prototype, the Java Conversion Tool from Microsoft was used to convert the J2EE Connector Architecture (JCA)/Common Client Interface (CCI) connector used by the M3 Mobile Sales Rich Client to J#. This allowed the client developers to use the J# client-side connector to interface with existing back-end systems. The generated J# connector was replaced for the production client, which utilizes WCF endpoints to access Web Services created by the Lawson Web Service Framework.

The Web Service Framework allows the generation of Web Services Definition Languages (WSDLs) and Java connector code from metadata provided from the business server. The Lawson developers restructured and reused existing server components from the J2EE Workplace Foundation application, allowing the AJAX based Workplace thin client and the new Lawson Smart Client to utilize the same business logic and services.

Drill down #1 - Java and .NET integration via Web Services

The new Lawson Smart Client utilizes features of the new Windows Presentation Foundation and Windows Communication Foundation. As can be seen in the diagram below, the client widget is dynamically bound to the C# data source using Extensible Application Markup Language (XAML). The data source utilizes a proxy object to communicate with the appropriate Web Service. The proxy, generated by the Service Model Metadata Utility Tool (SvcUtil.exe) in the Microsoft Windows Software Development Kit (SDK), handles communication with the web service and translation between the C# and Web-Service paradigms. Web service calls are performed using Simple Object Access Protocol (SOAP) over Hypertext Transfer Protocol (HTTP).

Sample of Web Service Call from Smart Client Widget

© 2007 Lawson Software, Inc. All rights reserved. This document is for informational purposes only. Lawson Software makes nowarranties, express or implied, in this summary.

Sample of Asynchronous Binding (XAML) binding the client widget to the datasource

<ObjectDataProvider x:Key="datasourceCRS990"
ObjectType="{x:Type ds:CRS990DataSource}" />

<ListView x:Name="SelectionList"
ItemsSource={Binding Source=dataSourceCRS990,
Path=ResultCollection, IsAsync=True, Mode=OneWay} >
<... />
</ListView>

Sample Code from the C# Datasource demonstrating the call to the Webservice Proxy

Public ObservableCollection<BrowseRow> ResultCollection
{
get
{
InitBrowseCollection data = new InitBrowseCollection();
data.InitBrowseItem = GetCallContext();
try {
CRS990MIClient crs990 =
WSHelper.CreateClient<CRS990MIClient, CRS990MI>();
InitBrowseResponseItem[] responseCollection = crs990.InitBrowse(
WSHelper.GetLWSCredentials<CRS990MIClient>(), data);
return FilterResponse(responseCollection);
}
}
}

Sample method from the generated WebService Proxy

...
public InitBrowseResponseItem[] InitBrowse(headerType mws,
InitBrowseCollection initBrowse)
{
InitBrowseRequest inValue = new InitBrowseRequest();
inValue.mws = mws;
inValue.InitBrowse = initBrowse;
InitBrowseResponse retVal =
((SmartClient.Widgets.WS.CRS990MI)(this)).InitBrowse(inValue);
return retVal.InitBrowseResponse1;
}
...

The heart of the Lawson M3 server-side application suite is the Web Service Framework. The framework, implemented in java, includes client-side design tools that allow the creation, testing, and hot-deployment of Web Services based on business system Application Programming Interfaces (APIs). These design tools generate code that handles mediation from the web service to the business APIs, parsing the Extensible Markup Language (XML) payloads of the web service requests and marshalling the responses from the business systems back into XML response payloads. The location and architecture of each backend API differ greatly. As an example, the M3 System is essentially a stand alone server-side application that is accessed through Transmission Control Protocol (TCP)/Socket based APIs to maximize performance.

As can be seen in the below diagram, all connecting material and configuration data that is needed to expose a business API as a web service is generated by the framework. A number of tools are utilized, including Apache XMLBeans for Java/XML bindings, and StAX (Streaming API for XML) for parsing and validation of the web service payloads. Also included are the generation of WSDLs, XML descriptors for the service, and the connector code required to invoke the particular business service.

The Lawson web server, deployed to the servlet container of an IBM® WebSphere® application server, is the run time engine that utilizes this generated code. Constructed around the Axis2 core engine for Web Services from the Apache Foundation, the web server also handles run-time configuration, connection pooling, and security. The Axis2 engine provides good performance and standards support including WS-I Basic Profile and WS Security (for encryption of messages). Isolation of web services is provided through the separation of classloaders - the classes of each generated web service are loaded by a discrete classloader.

Drill Down #2 - Rendering the UI from Metadata

Many of Lawson's previous business applications utilize XML for facets of their client/server communications. Due to continued use by older clients, the implementation of these services/applications could not be changed. Lawson had adapted to this constraint in various ways. The existing thin client application (AJAX/JSP based) utilizes Extensible Stylesheet Language Transformation (XSLT) to transform the combination of metadata and runtime data from XML to HyperText Markup Language (HTML). Javascript is included to handle all of the "extra stuff" that can't be accomplished through straight XSLT/HTML.

For their rich-client applications, the same XML data had to be used to create the UI. Since dynamic UI creation had been done in one of the previous Java rich-client applications, the pattern was adapted to C# for the Lawson Smart Client.

In order to enable the rendering of XML into a UI, a screen in the business application is first designed using a WYSIWYG form-building tool. Once the screen is defined, metadata is exported from the design tool that describes all of the controls on the newly created panel. This metadata also contains binding information that describes the information being retrieved from the server-side business application and how to interpret this data.

Sample of panel from the metadata description

© 2007 Lawson Software, Inc. All rights reserved. This document is for informational purposes only. Lawson Software makes no warranties, express or implied, in this summary.

A snippet of metadata describing a panel in the UI:

<Panel name="MMA001E0" rtype="DETAIL" modDateField="WMLMDT" regDateField="WMRGDT" changedByField="MMCHID">
<PanelHeader>MMS001/E</PanelHeader>
<PanelDescription langId="MM00101"/>
<Objects>
<GroupBox langId="MX_0235" justification="LEFT">
<Position left="1" top="1" width="73" height="1"/>
</GroupBox>
<Caption langId="WIT0115" tab="256">
<Position left="1" top="2" width="14" height="1"/>
</Caption>
<EntryField name="MMITNO" fieldHelp="ITNO" suppressLeadingZero="true" protected="IN41|!IN45" tab="270">
<Constraints maxLength="15" uppercase="UC"/>
<BrowsePosition top="4" left="18"/>
<Position left="15" top="2" width="16" height="1"/>
</EntryField>
<GroupBox langId="MX_0238" justification="LEFT">
<Position left="1" top="4" width="73" height="1"/>
</GroupBox>
<Caption langId="WNA0115" visible="!IN21" tab="1024">
<Position left="1" top="5" width="14" height="1"/>
</Caption>
<EntryField name="MMITDS" fieldHelp="ITDS" suppressLeadingZero="true" visible="!IN21"
protected="IN01|IN21|IN45&!IN41" tab="1038">
<Constraints maxLength="30"/>
<BrowsePosition top="5" left="18"/>
<Position left="15" top="5" width="31" height="1"/>
</EntryField>
<...>
<FunctionKeys value="001011111001000000000000">
<FunctionKey fKey="F3" langId="XF03000"/>
<FunctionKey fKey="F5" langId="XF05000"/>
<FunctionKey fKey="F6" langId="XF06000" visible="!IN41" reverse="!IN41&IN57"/>
<FunctionKey fKey="F7" langId="XF07000" visible="!(!IN45)"/>
<FunctionKey fKey="F8" langId="XF08000" visible="!(!IN45)"/>
<FunctionKey fKey="F9" langId="XF09000" visible="!IN41"/>
<FunctionKey fKey="F12" langId="XF12000"/>
</FunctionKeys>
</Objects>
<RecordFields length="634">
<RecordField name="WWCLIN" type="DECIMAL" pos="18" length="3" refField="CLIN"/>
<RecordField name="WWCPOS" type="DECIMAL" pos="21" length="3" refField="CPOS"/>
<RecordField name="MMITNO" pos="24" length="15" refFile="MITMAS" refField="MMITNO"/>
<...>
</RecordFields>
</Panel>

Each metadata file is then deployed to the web application. The web application combines the metadata with runtime data from the business applications and generates a new XML structure that includes both sets of data. This new XML format contains all information necessary for designating which UI widgets need rendering. The data includes the widgets' locations on the screen, formatting information (e.g., number of decimal places, etc.), and entry constraints (entry must be numeric). This form of XML is used by both the code that renders the AJAX/JSP thin client and the new Lawson Smart Client.

A snippet of XML showing the combination of metadata and runtime data:

<Panel name="MMA001E0">
<Objs>
<FKeys val="001011001001000000000000">
<FKey val="F3">End</FKey>
<FKey val="F5">Refresh</FKey>
<FKey val="F6">Text</FKey>
<FKey val="F9">Field Audit</FKey>
<FKey val="F12">Cancel</FKey>
</FKeys>
<EFld tab="270" name="MMITNO" hlp="ITNO" acc="WD">
<Pos l="15" t="2" w="16" h="1"/>
<Constr maxL="15" type="CHAR" uc="UC"/>
B000007
</EFld>
<EFld tab="1038" name="MMITDS" hlp="ITDS" acc="WE">
<Pos l="15" t="5" w="31" h="1"/>
<Constr maxL="30" type="CHAR"/>
Air Filter, Fleetguard AF1811
</EFld>
<...>
<ChkBox name="MMECMA" hlp="ECMA" tab="4149" acc="WE">
<Pos l="54" t="17" w="3" h="1"/>
0
</ChkBox>
<...>
<CBox name="MMSTCD" hlp="STCD" tab="4366" acc="WE">
<Pos l="15" t="18" w="16" h="1"/>
<CBV val="0">0-No inv account</CBV>
<CBV val="1" sel="true">1-Inv accounting</CBV>
<CBV val="2">2-No, but planned</CBV>
<CBV val="3">3-No, but as func</CBV>
</CBox>
<...>
<GroupBox r="t">
<Pos l="1" t="1" w="73" h="1"/>
Panel Header
</GroupBox>
<GroupBox r="t">
<Pos l="1" t="4" w="73" h="1"/>
Basic Information
</GroupBox>
<...>
</Objs>
<PHead>MMS001/E</PHead>
<PDesc>Item. Open</PDesc>
</Panel>

The Lawson Smart Client receives this XML document from the Web Server. A client side component interprets this XML, dynamically creates and positions all of the UI widgets, initializes the widgets with data and constraints, and adds them to a panel. The component also considers user and other UI settings during this process.

Example code used to dynamically render a label

private void ReadCaption(XMLNode n) {
string text = n.InnerText;
if (text.Length > 0) {
XMLAttribute a = n.Attributes["tip"];
string name = GetStringAttribute(n, "id");
string tooltip = a != null ? a.Value : null;
bool isFixed = n.Attributes["fixFnt"] != null;
bool isAdditionalInfo = n.Attributes["addInfo"] != null;
bool isEmphasized = n.Attributes["emp"] != null;
bool isColon = n.Attributes["cl"] != null;

CreateLabel(name, text, tooltip, isFixed, false, isAdditionalInfo, isEmphasized, isColon);
ReadPosition(n);
SetPosition();
SetWidth();

ReadAccess(n);
SetAccess();

AddElement();
}
}

protected void CreateLabel(string name, string text, string tooltip,
bool isFixed, bool wrap, bool isAdditionalInfo,
bool isEmphasized, bool isColon) {
Style s;
HorizontalAlignment hAlign = HorizontalAlignment.Left;

if (isAdditionalInfo) {
s = StyleManager.StyleAdditionalInfo;
} else if (isEmphasized) {
s = StyleManager.StyleEmphasized;
} else {
s = StyleManager.StyleLabel;
}

label = controlPool.Create(ControlPool.TypeLabel, s)
as Label;
currentElement = label;
label.Name = name;
CreateControlTag();
controlTag.AdditionalInfo = isAdditionalInfo;

if (wrap) {
TextBlock tb = new TextBlock();
tb.Text = text;
tb.TextWrapping = TextWrapping.Wrap;
label.Content = tb;
} else {
if (!isAdditionalInfo) {
if (isColon) {
// Only labels with colon can be right-aligned.
// The colon is only displayed for left align
if (UserSettings.Current.RightAlignLabels) {
hAlign = HorizontalAlignment.Right;
} else {
text += ":";
}
}
}
label.VerticalAlignment = VerticalAlignment.Center;
label.Content = text;
}

label.HorizontalAlignment = hAlign;

if (isFixed) {
label.FontFamily = StyleManager.FontFamilyFixed;
} else {
label.FontFamily = StyleManager.FontFamilyBaseUI;
}

label.ToolTip = tooltip;
}

Drill Down # 3 Using a rules engine to enhance system stability

The server-side of the Lawson application is designed to be deployed on multiple platforms. Customer installations are running on a variety of hardware and operating systems, as well as storing data in multiple RDBMS. The diverse nature of these environments makes maintaining and monitoring system performance a challenging task. Multiple layers of support might be necessary to resolve a system issue, potentially resulting in significant business delays and information loss.

In order to enhance the stability of their system, Lawson created the Foundation Stabilizer. The Foundation Stabilizer is a rule-based monitoring system that is based on common constraints and patterns defined by support and development personnel. It gathers system performance and configuration information during operation. This information can include data from the underlying operating system (such as CPU use), collected by interaction with Java Native Interface (JNI) based components. When a potential issue is identified, the Stabilizer will issue warnings to support personnel (via Real Simple Syndication, email, or html) and take actions where applicable (such as lowering the guilty thread's priority) to help ensure a stable system foundation. A large amount of system data that is gathered for manual system monitoring can be used as a basis for rule and pattern matching:

  • Performance counters
  • Data from property files
  • Java parameters and properties
  • System environment information
  • Job information, job status, activity levels, etc.
  • Java Virtual Machine (JVM) status and resource consumption
  • Platform specific or independent constraints for property values

Example 3: A warning issued by the Foundation Stabilizer. © 2007 Lawson Software, Inc. All rights reserved. This document is for informational purposes only. Lawson Software makes no warranties, express or implied, in this summary.

The rules engine is built in Java and works on rules written in Prolog. The stabilize() method, below, is regularly invoked at a configurable interval by a looping Java thread. This java thread runs in the same JVM as the business application.

 boolean stabilize() throws Exception {
KnowledgeBase kb = engine.getKnowledge();

engine.consult("mvx/res/Stabilizer.rules");
engine.consult("mvx/res/Constraints.rules");

engine.setQuery("cleanup");

try {
addSysInfo(kb);
} catch (IOException ex) {
KQLOG.EX("MvxStabilizer:Failed to collect system information", ex);
return false;
}

KQLOG.DT("Run Stabilizer");
boolean res = engine.setQuery("stabilize");

if (!res) {
KQLOG.E("Could not stabilize - rule engine failed");
return false;
}
KQLOG.DT("End Stabilizer");

Enumeration en = kb.elements();

while (en.hasMoreElements()) {
// Handle results from the rules engine
}

return true;
}
  • The method commences with the extraction of the existing knowledge base from the rules engine. The knowledge base contains facts about the system, generated and calculated. The facts are simple expressions in the form operator term or operator term1 term2. Typical examples might include the following: "jobName" "IKDXLZ9A.corp.lawson.net:26100" 4676 "Looping" "jobCPU" "IKDXLZ9A.corp.lawson.net:26100" 4676 55
  • Rules (in this example, two sets) are associated with the engine.
  • Old information is removed from the knowledge base by evaluating "cleanup"
  • The knowledge base is refreshed with current system information.
  • New facts are gathered by rules fired by evaluating "stabilize"
  • When all rules are evaluated, the entire knowledge base is rechecked for new facts, and the results are handled.
...
rule(looping_nostat1):-
jobCPU(Addr, Id, CPU),
CPU>40,
jobChange(Addr, Id, Change),
(Change==0;Change>999),
not(loopingMem(Addr, Id, _)),
info(currentTime, Time),
assert(loopingMem(Addr, Id, Time)).

rule(looping_nostat2):-
loopingMem(Addr, Id, LTime),
jobCPU(Addr, Id, CPU),
CPU>40,
jobChange(Addr, Id, Change),
(Change==0;Change>999),
info(currentTime, Time),
Time-LTime>5,
jobType(Addr, Id, JobType),
jobName(Addr, Id, Name),
assert(warning(2, 4, 'Job may be looping', 'Job', JobType, Addr, Name, Id)).

rule(looping_nostat3):-
loopingMem(Addr, Id, LTime),
not(jobName(Addr, Id, _)),
retract(loopingMem(Addr, Id, LTime)).
...

The three rules above are used to detect jobs using too much CPU.

The first rule detects any job using too much CPU and starts to monitor the job. A job with a certain address, ID and CPU usage is selected based on the fact jobCPU. If the job is using more than 40% CPU and the rate of change is either zero or > 999, a new fact is created (loopingMem) and Time is set.

The second rule detects a job that has been monitored for more than five minutes and reports it. The fact loopingMem (set by the first rule) is used to search through the knowledge base for these jobs. The same facts are checked as were checked in the first rule. Time is updated, and the facts jobType and jobName are added. These facts are used to present a warning in the M3 Business Engine.

The third rule detects jobs that have stopped executing and removes them from the observation list. The fact loopingMem is used to search through the knowledge base for these jobs. If the job cannot be found in the knowledge base, the entry is removed from the observation list.

Conclusion

The merger of Lawson and Intentia in 2006 left developers with an important problem to solve - the integration and presentation of legacy applications and business services that are constructed in a number of disparate technologies. In order to provide a homogeneous user experience across both of the corporation’s flagship ERP product lines, the client solution would be required to interact with business tiers based on both Java and 4GL languages.

After analyzing user interface technologies with the assistance of frog design europe gmbh, Lawson decided to leverage Microsoft’s .NET 3.0 framework. Utilizing Windows Presentation Foundation (WPF) data binding to tie UI components to business logic / data components and the Windows Communication Foundation (WCF) to communicate with their pre-existing web services framework (with some minor restructuring), Lawson developers were able to quickly produce a client application that met their needs. The new Lawson Smart Client defines a pluggable client architecture capable of hosting new applications and application functionality, while utilizing legacy business logic via web services. Lawson’s legacy applications are capable of leveraging these same business services, increasing maintainability by enabling the use of one code base across the enterprise.

In the process of creating the Lawson Smart Client, their developers learned a significant lesson about the nature of declarative programming languages. On the downside, declarative languages can have a rather large learning curve, are not always easy to use, and are not necessarily intuitive. However, once one develops the mindset to correctly leverage these languages, there can be large gains in productivity. In the experience of the Lawson developers, this increase in productivity seems to be worth the initial effort in learning the paradigm.

The original vision of the Lawson Smart Client architecture was to create a new breed of applications with powerful and flexible user interface components. The aim was to present customers with a seamless user experience across Lawson’s flagship product lines while increasing user productivity and satisfaction. Initial customer reaction indicates that Lawson has, indeed, been successful in this endeavor.

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread
Community comments

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Discuss

Educational Content

General Feedback
Bugs
Advertising
Editorial
InfoQ.com and all content copyright © 2006-2014 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT