Step-by-Step Legacy Migration with Aranea Jevgeni Kabanov R&D lead, Aranea project lead...

Preview:

Citation preview

Step-by-Step Legacy Migration with Aranea

Jevgeni KabanovR&D lead, Aranea project leadWebmedia, 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

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

Recommended