step-by-step legacy migration with aranea jevgeni kabanov r&d lead, aranea project lead...
TRANSCRIPT
Step-by-Step Legacy Migration with Aranea
Jevgeni KabanovR&D lead, Aranea project leadWebmedia, 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!
NameWidgetname.jspReads the name from requests and passes it to HelloWidget
HelloWidgethello.jspRenders 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: <input type=“text“ name=“${widgetId}.name"/><br/><br/> <ui:eventButton labelId="#Say hello" eventId="hello"/>
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}! <br/><ui:eventButton labelId="#Back" eventId="back"/>
hello.jsp:
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
FlowsWhat 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
<ui:widgetInclude id="hello"/><br/>Insert your name: <input type="text“ name=“${widgetId}.name"/><br/><br/> <ui:eventButton labelId="#Say hello" eventId="hello"/>
And finally we include the widget in the JSP
Including widgets
So this is what we get:
helloWidget.setName(“Jevgeni”)
HelloWidget, helloWidget<ui:widgetInclude id=“hello”/>
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<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 & RootWidgetpublic class HelloNameWidget extends StrutsWidget { public HelloNameWidget() { super("/name.jsp"); }}
public class RootWidget extends BaseUIWidget { protected void init() { addWidget("hello1", new HelloNameWidget()); setViewSelector("root"); }}
<input name="f0.hello1.name" type="text"/> <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"); }}
<ui:widgetInclude id="hello1"/><br/><br/><ui:widgetInclude id="hello2"/><br/><br/><ui:widgetInclude id="hello3"/>
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 <a> 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(),
<ui:hostedWidgetInclude> 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 (www.webmedia.eu) is a Baltic
company employing over 300 people that
sponsors Aranea development
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
www.araneaframework.com
www.araneaframework.org