640 likes | 657 Views
Step-by-Step Legacy Migration with Aranea. Jevgeni Kabanov R&D lead, Aranea project lead Webmedia, Ltd. ekabanov@webmedia.ee. Motivating scenario.
E N D
Step-by-Step Legacy Migration with Aranea Jevgeni Kabanov R&D lead, Aranea project lead Webmedia, Ltd. ekabanov@webmedia.ee
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 • Use Aranea webintegrationlayer to run different technologies side-by-side • Refactor the application into independent coarse-grained components • 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-classwidgets
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 publicclass NameWidget extends BaseUIWidget { //Called on “hello” event publicvoid handleEventHello() { Stringname = //reads “name” from request parameters (String) getScopedData().get("name"); getFlowCtx().replace(new HelloWidget(name)); } } name.jsp: Insert your name: <input type=“text“ name=“${widgetId}.name"/><br/><br/> <ui:eventButton labelId="#Say hello" eventId="hello"/>
HelloWidget publicclass 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() { returnthis.name; } public void handleEventBack() { getFlowCtx().replace(new NameWidget()); } } hello.jsp: Hello ${widget.name}! <br/> <ui:eventButton labelId="#Back" eventId="back"/>
web.xml ... <servlet> <servlet-name>araneaServlet</servlet-name> <servlet-class>AraneaSpringDispatcherServlet</servlet-class> <init-param> <param-name>araneaApplicationStart</param-name> <param-value>example.NameWidget</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>araneaServlet</servlet-name> <url-pattern>/main/*</url-pattern> </servlet-mapping> ...
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 HelloWidget handleEventHello() We assume that the “back” button was removed from HelloWidget
Including widgets • First let’s modify the HelloWidget: publicclass HelloWidget extends BaseUIWidget { private String name; public HelloWidget(String name) { this.name = name; } public String getName() { returnthis.name; } publicvoid setName(String name) { this.name = name; } }
Including widgets • Now let’s add a HelloWidget instance publicclass NameWidget extends BaseUIWidget { privateHelloWidget helloWidget; protectedvoidinit() { helloWidget = new HelloWidget("Stranger"); addWidget("hello", helloWidget); } publicvoid handleEventHello() { String name= (String) getScopedData().get("name"); helloWidget.setName(name); } }
Including widgets • And finally we include the widget in the JSP <ui:widgetInclude id="hello"/><br/> Insert your name: <input type="text“ name=“${widgetId}.name"/><br/><br/> <ui:eventButton labelId="#Say hello" eventId="hello"/>
Including widgets • So this is what we get: HelloWidget, helloWidget <ui:widgetInclude id=“hello”/> helloWidget.setName(“Jevgeni”)
Widgets are objects • We can include several widgets of same class on one page publicclass RootWidget extends BaseUIWidget { protectedvoid init() { addWidget("hello1", new NameWidget()); addWidget("hello2",new NameWidget()); addWidget("hello3", new NameWidget()); } }
Flows are objects • We can also include several flow containers on one page publicclass RootWidget extends BaseUIWidget { protected void init() { addWidget("flowContainer1", new StandardFlowContainerWidget(new NameWidget())); addWidget("flowContainer2", new StandardFlowContainerWidget(new NameWidget())); addWidget("flowContainer3", new StandardFlowContainerWidget(new NameWidget())); } }
Goal Get rid of legacy and custom web frameworks in your application
Our Solution • Use Aranea webintegrationlayer to run different technologies side-by-side • Refactor the application into coarse-grained integration components • 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 • Session and request attributes share the same namespace and can clash • Request parameter names will clash already during form submission • Struts navigates between pages by changing the actual URL • 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 <html> <body> <form method="get" action="<%=response.encodeURL("hello.jsp")%>"> <input name="name" type="text"/> <input type="submit" value="Say hello!"> </form> </body> </html> <html> <body> Hello ${param.name}! <a href="<%=response.encodeURL("name.jsp")%>">Back</a> </body> </html>
HelloNameWidget & RootWidget publicclass HelloNameWidget extends StrutsWidget { public HelloNameWidget() { super("/name.jsp"); } } publicclass RootWidget extends BaseUIWidget { protectedvoid init() { addWidget("hello1", new HelloNameWidget()); setViewSelector("root"); } } Output: <input name="f0.hello1.name" type="text"/> <input type="submit" onclick=“new Aranea.Struts.Form(…).submit()" value="Say hello!">
What will happen? publicclass RootWidget extends BaseUIWidget { protectedvoid init() { addWidget("hello1", new HelloNameWidget()); addWidget("hello2", new HelloNameWidget()); addWidget("hello3", new HelloNameWidget()); setViewSelector("root"); } } <ui:widgetInclude id="hello1"/><br/><br/> <ui:widgetInclude id="hello2"/><br/><br/> <ui:widgetInclude id="hello3"/>
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!
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!
Goal Get rid of legacy and custom web frameworks in your application
Our Solution • Use Aranea webintegrationlayer to run different technologies side-by-side • Refactor the application into coarse-grained integration components • Start new development immediately and change old code only when requirements change – step-by-step migration
Refactoring • Enable Aranea Integration and sanitize HTML (produces working application) • Extract layout, menu and login • 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