Step-by-Step Legacy Migration with Aranea Jevgeni Kabanov R&D lead, Aranea project lead Webmedia, Ltd.
Motivating scenario “Stakeholders have a large web application written in Struts. They consider Struts legacy and want to continue development in JSF. However rewriting all of the code would take too much time, effort and money and would halt the ongoing development.“
Our solution 1.Use Aranea web integration layer to run different technologies side-by-side 2.Refactor the application into independent coarse-grained components 3.Start new development immediately and change old code only when requirements change – step-by-step migration
Goal Get rid of legacy and custom web frameworks in your application
Aranea Aranea began as an Object-Oriented MVC Web Framework From the onset we had plans to build web integration on the same platform Aranea Integration has been released to public yesterday :)
Disclaimer Aranea MVC is stable and used in production Aranea Integration is beta and used in pilot migration projects Everything is Open-Source with documentation and support available for free from araneaframework.org Commercial support/training/consulting is provided at araneaframework.com
Organization Aranea Component Model Widgets Flow navigation Aranea Integration Layer Struts, JSF, GWT Step-by-step migration Principles Case study
Aranea Component Model Every component is a first-class object Objects are created by the programmer No (XML) mappings State is in the object (no scopes) Components, pages and flows are represented by first-class widgets
Hello World! NameWidget name.jsp Reads the name from requests and passes it to HelloWidget HelloWidget hello.jsp Renders the “Hello ${name}!” greeting, where name is given by the caller.
NameWidget public class NameWidget extends BaseUIWidget { //Called on “hello” event public void handleEventHello() { String name = //reads “name” from request parameters (String) getScopedData().get("name"); getFlowCtx().replace(new HelloWidget(name)); } Insert your name: name.jsp:
HelloWidget public class HelloWidget extends BaseUIWidget { private String name; //Widget state is in its fields public HelloWidget(String name) { this.name = name; //We could pass any Java object here } public String getName() { return this.name; } public void handleEventBack() { getFlowCtx().replace(new NameWidget()); } Hello ${widget.name}! hello.jsp:
web.xml... araneaServlet AraneaSpringDispatcherServlet araneaApplicationStart example.NameWidget 1 araneaServlet /main/*...
Flows Currently we use replace() which means: A new instance is created every time We know where to return Flow1
Flows What we would want is to preserve the instance and nest the new flow Flow1 Flow2
Flows start() and finish() do exactly that: public class NameWidget extends BaseUIWidget {... public void handleEventHello() {... getFlowCtx().start(new HelloWidget(name)); } public class HelloWidget extends BaseUIWidget {... public void handleEventBack() { getFlowCtx().finish(null); }
Including widgets Widgets can be included, let’s try to use HelloWidget inside NameWidget like this handleEventHello() HelloWidget We assume that the “back” button was removed from HelloWidget
Including widgets First let’s modify the HelloWidget: public class HelloWidget extends BaseUIWidget { private String name; public HelloWidget(String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; }
Including widgets Now let’s add a HelloWidget instance public class NameWidget extends BaseUIWidget { private HelloWidget helloWidget; protected void init() { helloWidget = new HelloWidget("Stranger"); addWidget("hello", helloWidget); } public void handleEventHello() { String name = (String) getScopedData().get("name"); helloWidget.setName(name); }
Including widgets Insert your name: And finally we include the widget in the JSP
Including widgets So this is what we get: helloWidget.setName(“Jevgeni”) HelloWidget, helloWidget
Widgets are objects We can include several widgets of same class on one page public class RootWidget extends BaseUIWidget { protected void init() { addWidget("hello1", new NameWidget()); addWidget("hello2", new NameWidget()); addWidget("hello3", new NameWidget()); }
Flows are objects public class RootWidget extends BaseUIWidget { protected void init() { addWidget("flowContainer1", new StandardFlowContainerWidget(new NameWidget())); addWidget("flowContainer2", new StandardFlowContainerWidget(new NameWidget())); addWidget("flowContainer3", new StandardFlowContainerWidget(new NameWidget())); } We can also include several flow containers on one page
Goal Get rid of legacy and custom web frameworks in your application
Our Solution 1.Use Aranea web integration layer to run different technologies side-by-side 2.Refactor the application into coarse-grained integration components 3.Start new development immediately and change old code only when requirements change – step-by-step migration
Requirements We want to implement widgets using any framework/technology available This can mean running a whole application in one widget and another application in its sibling Without any changes to the technology In fact we want to do that retroactively, reusing existing applications
Aranea Integration Layer Integration Layer API is based around widgets: StrutsWidget, JsfWidget, GwtWidget Widgets receive the URI of the starting point of the subapplication E.g. new StrutsWidget(“/Welcome.do”); AraneaUtil gives access to all of the Aranea API from embedded applications
Struts Integration Problems 1.Session and request attributes share the same namespace and can clash 2.Request parameter names will clash already during form submission 3.Struts navigates between pages by changing the actual URL 4.HTML limits usage of some tags
Problem 1: Attributes We can make request attributes local, by wrapping HttpServletRequest and saving them in a local map Since HttpSession can only be accessed via HttpServletRequest we can do the same thing to session attributes
Problem 2: Parameters Since parameter names clash already during the submission of request we need to solve the problem in HTML We can do it by introducing prefixes to each field name referring to the containing widget The request wrapper restores the original names
Problem 3: Navigation We put a filter over the Struts servlet that will include the Aranea servlet While Aranea renders the particular StrutsWidget, it will include the according action Therefore it will render in correct place as will everything else
Problem 3: Navigation However we need to include some information that is used to render Aranea Servlet path Window id Current widget id We do that by overriding encodeURL() in request wrapper
Problem 4: HTML There are two main things we need to change in Struts HTML output: Forms cannot be nested and must be escaped Field names must be prefixed These are easy to change using a lexer (not even a parser) on the output stream To escape forms we construct a JavaScript object with the same properties/methods
name.jsp & hello.jsp <form method="get" action=" "> Hello ${param.name}! ">Back
HelloNameWidget & RootWidget public class HelloNameWidget extends StrutsWidget { public HelloNameWidget() { super("/name.jsp"); } public class RootWidget extends BaseUIWidget { protected void init() { addWidget("hello1", new HelloNameWidget()); setViewSelector("root"); } <input type="submit" onclick=“new Aranea.Struts.Form(…).submit()" value="Say hello!"> Output:
What will happen? public class RootWidget extends BaseUIWidget { protected void init() { addWidget("hello1", new HelloNameWidget()); addWidget("hello2", new HelloNameWidget()); addWidget("hello3", new HelloNameWidget()); setViewSelector("root"); }
DEMO
Generalizing Integration The approach taken with Struts can be easily extended to any action-based framework Including custom ones In fact most of it is applicable to component- based frameworks as well However component-based frameworks will usually allow us to solve these problems simpler
JSF Integration We use the same approach to encapsulate request and session attributes Form fields can be prefixed by overriding the naming container (form) Navigation can be solved by overridding the view handler No postprocessing necessary!
DEMO
GWT Integration Essentially the simplest, as almost everything happens on the client side Two problems Using RPC to call widget methods Restoring client-side state after a full request Not solved yet!
Our Vision
Goal Get rid of legacy and custom web frameworks in your application
Our Solution 1.Use Aranea web integration layer to run different technologies side-by-side 2.Refactor the application into coarse-grained integration components 3.Start new development immediately and change old code only when requirements change – step-by-step migration
Refactoring 1.Enable Aranea Integration and sanitize HTML (produces working application) 2.Extract layout, menu and login 3.Split application into coarse-grained components
Case Study Estonian Tax Inspection application module Connected with Forestry and European Union directives Part of a large application family based on common architecture that manage all tax and customs needs
Technologies MVC web framework is Struts Presentation done using Velocity and Tiles A lot of custom extensions to all of them SSO using Weblogic API
Step 1: Integration & HTML Application started running after initial configuration HTML tags were used in some places, which means encodeURL() was not applied Some scripts accessed form fields by name Added a widget prefix before the name
Step 2: Layout, menu and login Since login was done using SSO we left it be We extended the Aranea MenuWidget to implement the menu from scratch, reusing all the old privileges After lifting header, footer and menu to the RootWidget turned out that Tiles were not doing anything useful anymore and could be eliminated
Step 3: How to split? First we extract the widgets pointing to the relevant parts of the application Next we change all navigation/inclusion between those parts to use Aranea API: Sending events to the hosting widget AraneaUtil.getFlowCtx(), AraneaUtil.addWidget(), used from the embedded applications
Step 3: Analyze and split After some research we decided that the best way to split the application would be vertically – by functionality We ended up with five different functionality types and about ten different widgets (some widgets were multipurpose)
Step 3: Analyze and split Some widgets were used only as flows while others were included as components By extracting and abstracting several included widgets we eliminated a lot of copy-paste While we could further refine our components it was good enough for starting migration
Results Five main functionality parts which could be rewritten one by one without affecting the others No more Tiles Less copy-paste If we needed to add a sixth functionality part we could start using JSF immediately
Goal Get rid of legacy and custom web frameworks in your application
Our solution 1.Use Aranea web integration layer to run different technologies side-by-side 2.Refactor the application into coarse-grained integration components 3.Start new development immediately and change old code only when requirements change – step-by-step migration
Step-by-step migration After refactoring every component is a Java class with a particular interface/contract The rest of the components can only interact with it via that contract Therefore we can just rewrite it using any other implementation as long as the contract is preserved
Case study: migration In the case study we wanted to use Aranea, so there was no need for further migration Eventually we would like to lift the whole Tax and Customs application to Aranea using other framework features when needed However we also have clients who prefer JSF and Tapestry, so in those cases we would continue
The Next Step Aranea Integration solves the problem of mashing up Java web applications A lot of legacy applications are written in Perl, PHP, Oracle Forms, etc You’d also want to integrate with.NET web applications Aranea Remote Integration will support that!
Webmedia Webmedia ( is a Baltic company employing over 300 people that sponsors Aranea developmentwww.webmedia.eu Webmedia offers complete commercial support for Aranea MVC & Integration In fact we now also offer support for migrating your legacy web applications to a platform of your choice :)
Final Words Using Aranea Integration is easy Problems might come up requiring better understanding of Integration works Migration is not completely painless, but it is cheap next to the alternative Migrated Struts Mailreader application in the distribution is a good starting point
Questions