520 likes | 831 Views
WEBWORK 2. “Strutting the OpenSymphony way”. Prepared by Mike Cannon-Brookes - June, 2003 mike@atlassian.com - http://www.atlassian.com. Agenda. WebWork 2 & XWork overview Actions Views Interceptors Validation Inversion of Control (IoC) Struts Comparison. WebWork 2 Overview.
E N D
WEBWORK 2 “Strutting the OpenSymphony way” Prepared by Mike Cannon-Brookes - June, 2003 mike@atlassian.com - http://www.atlassian.com
Agenda • WebWork 2 & XWork overview • Actions • Views • Interceptors • Validation • Inversion of Control (IoC) • Struts Comparison
WebWork 2 Overview • ‘Pull-based’ MVC framework • Focus on componentization & code reuse • Implementation of the command pattern • Second generation of WebWork • Not tied to the web! • Currently in beta - but being well used
XWork • Generic command pattern framework • Commands represent a unit-of-work • Split off from Webwork 1 • Why command pattern? WebWork 2 Web WebWork 1 XWork 1 Non-web
XWork provides… • Core command pattern framework for request / response environment • Interceptor framework • Action chaining • IoC componentization framework • Runtime attribute validation framework • Built-in type conversion using OGNL • Doesn’t provide: anything to do with the web!
WebWork 2 provides.. • Tied to HTTP request / response • Integration with session / application scopes • Servlet-based dispatcher to turn incoming requests into action/s. • Automatically set properties of action based on request parameters • View integration (JSP, Velocity etc) • User interface / form components
Actions • An Action is a command. • Each action should be a ‘unit of work’ • Actions should be simple! • Action interface has only one method: interface Action { String execute() throws Exception; } • Let’s look at small example…
Basic Example: Add Pet Use case - we want to add a Pet to our system:
Basic Example: Add Pet • A basic example of an action, view and configuration. • Model: Pet.java (simple bean) • Controller: AddPet.java (WW action) • View: addpet.jsp • Config: xwork.xml
Basic Example: Pet Model . . . public class Pet { private long id; private String name; public long getId() … public void setId(long id) … public String getName() … public void setName(String name) … }
Basic Example: AddPet action public class AddPet implements Action { protected Pet pet = new Pet(); public String execute() throws Exception { if (pet.getName() == null ) return ERROR; Registry.getPetStore().savePet(pet); return SUCCESS; } public Pet getPet() { return pet; } }
Basic Example: addpet.jsp <%@ taglib uri= "webwork" prefix= "webwork" %> <html> <head><title>Add A Pet</title></head> <body> <form action= "AddPet.action"> <webwork:textfield label="Name" name="pet.name" /> <input type= "submit" value= "Add"> </form> </body> </html>
Basic Example: xwork.xml <xwork> <package name="default"> . . . <action name="AddPet” class="org.petsoar...AddPet"> <interceptor-ref name="defaultStack" /> <result name="error">addpet.jsp</result> <result name="success">success.jsp</result> </action> . . . </package> </xwork>
ActionSupport • Useful base class, providing: • error message support • action and field specific errors • field errors are automatically supported by views • internationalisation support • 1 resource bundle per action • pervasive UI support for retrieving messages
Model-Driven vs Field-Driven • 2 types of Actions possible: • Model-driven • Action has methods returning your model classes (myAction.getPet()) • Fields in the view are fields of your model • Views refer directly to your model (property=‘pet.name’) • Excellent because it allows model reuse • Field-driven • Action has fields of its own, which are fields in the view • execute() collates fields and interacts with the model • Views refer to action fields (property=‘name’) • Useful where form is not parallel to model
Action Composition • Problem: traditional MVC actions contain duplication or deep class hierarchies • Solution: A single WW action can be composed of multiple smaller reusable beans. • Before: public class Signup implements Action { public String getName(); [+ setter] public String getHomeInternationalCode(); [+ setter] public String getHomeAreaCode(); [+ setter] public String getHomeNumber(); [+ setter] public String getWorkInternationalCode(); [+ setter] public String getWorkAreaCode(); [+ setter] public String getWorkNumber(); [+ setter] ... }
Action Composition • After: public class Signup implements Action { public String getName(); [+ setter] public PhoneNumber getHome(); public PhoneNumber getWork(); ... } public class PhoneNumber { public String getInternationalCode(); [+setter] public String getAreaCode(); [+setter] public String getNumber(); [+setter] } • We can also reduce duplication in our views in the same way - using UI components.
Action Dispatching • A Dispatcher configures and executes an action. • WebWork has ServletDispatcher and a FilterDispatcher for the servlet environment • XWork separates the implementation and invocation of an action • ClientDispatcher allows actions created by a client to be executed on server • execute an action over RMI (ie in an applet) • execute an action via SOAP
WebWork Views • Multiple supported view technologies: • JSP • Velocity • XML • JasperReports • … add your own • Not being tied to the web allows multiple pluggable ‘result types’ • - ie action chains, pooling, HTTP redirects etc
View Expression Language • For expressions WW uses OGNL (Object Graph Navigation Language) • Incrementally compiled expressions - fast! • Easy to learn, powerful expression language • Componentised (so you can embed anywhere) • Embedded everywhere - views and *.xml • Independently run Open Source project - http://www.ognl.org
UI Components • Powerful for componentization of views • Standard form components are built in • text field, radio boxes, submit button etc. • Skinnable using ‘themes’ • multiple sets of templates to render same components • Usable from any view • JSP or Velocity at the moment
JSP: <ui:textfield label="Username" name="username" /> <ui:password label="Password" name="password" /> <ui:component template="/mytemplate.vm"> <ui:param name="param1" value="value1" /> </ui> <ui:submit value="'login'" align="right" /> Velocity: #tag (TextField "label=Username" "name=username") #tag (Password "label=Password" "name=password") #bodytag (Component "template=/mytemplate.vm") #param ("name=param1" "value=value1") #end #tag (Submit "value='login'" "align=right") UI Component Usage
Component Rendering • <webwork:textfield label="Name" name="project.name" /> looks as follow (with added header) : • UI components automatically present field error messages, added by validation framework or action itself:
Component Rendering • Uses Velocity to actually render HTML fragments, eg in your JSP view: <webwork:textfield label="Name" name="project.name" /> renders via textfield.vm: #parse( "/decorators/xhtml/controlheader.vm" ) <input type="text" name="${tag.Name}" value="$!{tag.ActualValue}" #if($tag.Size > 0) size="${tag.Size}"#end /> #parse( "/decorators/xhtml/controlfooter.vm" )
Custom components • WW allows you to easily create custom UI components • Requires writing a single Velocity template • Excellent for componentizing views (with componentized or model-driven actions) • Example: a date picker to allow users to enter dates into text fields easily…
Custom component example • Here’s the form field and popup:
Custom component example • View (addpet.jsp): <webwork:component label="Created After" template="datepicker.vm" name="pet.created"> <webwork:param name="'formname'" value="'editform'" /> </webwork:component> • Component template (datepicker.vm) #parse( "/decorators/xhtml/controlheader.vm" ) <script language="JavaScript" src="/decorators/datepicker.js" /> <input type="text" name="${tag.Name}" value="$!{tag.ActualValue}" /> <a href="javascript:show_calendar('${tag.Params.get("formname")}', '${tag.Name }');"><img src="/images/icons/cal.gif"></a> #parse( "/decorators/xhtml/controlfooter.vm" )
Interceptors • “Practical AOP” • very simple, no external dependencies • allows you to intercept action invocations. • Help decouple and componentize your code • Interceptors are organized into ‘stacks’ • lists of interceptors applied in sequence. • applied to any action or package of actions • WebWork is mostly implemented as a series of XWork interceptors!
Timing Interceptor • A simple invocation interceptor: public class TimerInterceptor implements Interceptor { . . . public String intercept(ActionInvocation dispatcher) ...{ long startTime = System.currentTimeMillis(); String result = dispatcher.invoke(); long exTime = System.currentTimeMillis() - startTime; log.info(dispatcher.getProxy().getActionName() + " ran in " + exTime + "ms."); return result; } }
Logging Interceptor • A before/after processing interceptor: public class LoggingInterceptor extends AbstractInterceptor { . . . protected void before(ActionInvocation invocation) ... { log.info("Starting execution stack for action " + invocation.getProxy().getActionName()); } protected void after(ActionInvocation invocation, String result) ...{ log.info("Finishing execution stack for action " + invocation.getProxy().getActionName()); } }
Complex Interceptor • Problem: notifying users of events within our application via email • Solution: an XWork interceptor + XML config file • The interceptor: • parses the config file (if not loaded yet) • intercepts the action • matches action class & result to determine if any email needs to be sent • if it does, processes a Velocity template (email body) and sends it
Complex Interceptor - class public class EventNotifierInt extends AbstractInterceptor { . . . protected void after(ActionInvocation actionInvocation, String result) . . . { List listeners = getListenersFor(actionInvocation, result); for (int i = 0; i < listeners.size(); i++) { ConfEventListener l = (ConfEventListener)listeners.get(i); l.onEvent( result, actionInvocation.getAction() ); } } private void loadXmlConfiguration() ... private List getListenersFor(ActionInvocation invocation, String result) . . . }
Validation Framework • Validation of action properties • Decoupled from actions • validations stored in XML files • error messages stored in actions, flow through to UI components • Pluggable validator classes • Validation is implemented as an interceptor • You control when validation happens
Example Validation • adduser-validation.xml <validators> <field name="username"> <field-validator type="requiredstring"> <message>Please specify a username.</message> </field-validator> </field> <field name="confirm"> <field-validator type="fieldexpression"> <param name="expression"> confirm == null || password.equals(confirm) </param> <message key="passwords.dontmatch">no i18n msg!</message> </field-validator> </field> </validators>
Validator Class • checks that a String field is non-null and has a length > 0 public class RequiredStringValidator extends FieldValidatorSupport { public void validate(Action action) throws ValidationException { String fieldName = getFieldName(); Object value = this .getFieldValue(fieldName, action); if (!(value instanceof String) || value == null || "".equals((String) value)) { addFieldError(fieldName, action); } } }
What is Inversion of Control? • IoC removes the onus of managing components from your business logic into a container. • Container manages lifecycles and dependencies between components. • EJB is IoC, but with a static list of services • Security, persistence and transactions • The Jakarta Avalon project is all about IoC.
Advantages of IoC • Promotes simplicity and decoupling • Components describe themselves • Dependencies are discovered automatically • Adheres to Law of Demeter • Classes coupled to only what they use • Encourages smaller responsibility classes • Leads to better interface/impl separation • Unit tests become far simpler • they become ‘mini-containers’
IoC in XWork • First off: IoC can be controversial - it is optional! Use it if it suits you :) • XWork and WW provide a web-native IoC architecture • Components specify only which services they require • via interfaces (eg ShoppingCartAware) • Configuration file defines component implementations and scopes.
Component Scopes • WW has 4 component ‘scopes’ (lifetimes): • Application • HTTP Session • HTTP Request • XWork Action • Let’s look at an example with 2 services…
IoC Example Service #1 • A ShoppingCart service - provides a user’s cart (session scoped) ShoppingCartAware.java: public interface ShoppingCartAware { public void setShoppingCart(ShoppingCart cart); } ShoppingCart.java: public interface ShoppingCart { public void addPet(Pet pet); public void removePet(Pet pet); public boolean isEmpty(); public int size(); public List getPets(); }
IoC Example Service #2 • A PetStore service - provides management of our pet inventory (application scoped) PetStoreAware.java: public interface PetStoreAware { public void setPetStore(PetStore store); } PetStore.java: public interface PetStore { void savePet(Pet pet); void removePet(Pet pet); List getPets(); Pet getPet( long id); }
IoC Example -Being serviced! public class AddToCart implements Action, PetStoreAware, ShoppingCartAware { . . . public void setPetStore(PetStore ps) { this.petStore = ps; } public void setShoppingCart(ShoppingCart c) { this.cart = c; } public String execute() throws Exception { if (cart == null || petId == 0) return ERROR; Pet pet = petStore.getPet(petId); cart.addPet(pet); return SUCCESS; } }
IoC Example - Config • These services are configured in components.xml like so: <components> <component> <scope>application</scope> <class>org.petsoar.pets.DefaultPetStore</class> <enabler>org.petsoar.pets.PetStoreAware</enabler> </component> <component> <scope>session</scope> <class>org.petsoar.cart.SimpleShoppingCart</class> <enabler>org.petsoar.cart.ShoppingCartAware</enabler> </component> </components>
Action Packaging • A package is a JAR containing: • Actions, views, interceptors, validators, i18n properties and configuration • Packages are: • namespace aware • hierarchical • inherit capabilities from super packages • Promotes componentisation
Packaging Configuration • xwork.xml: <xwork> <package name="default"> . . . </package> <package name="subpackage" extends="default"> . . . </package> <include file="myotherpackage.xml" /> </xwork>
Struts vs WebWork • Jakarta Struts is the 500-lb gorilla of the MVC ‘space’ • No MVC presentation complete without a Struts comparison • I’ll try to be unbiased as possible :) • Not an apples-for-apples comparison • Think of it as a list of differences
WebWork Pros • Simpler framework • No more writing ‘junk code’ to fulfill Struts’ contracts • No more actionbean/formbean classes • Use simple model-driven actions and your own model • Actions are easy to unit test • Instantiate, call setters, run execute() • WW ‘plays well with others’ • Multiple view technologies well supported
WebWork Pros • Simpler views • more powerful expression language • no more pages with 1000’s of Struts tags • No need to make actions thread safe • One action instantiated per request • Actions not coupled to the web • Can be invoked remotely eg ClientDispatcher & RMI • Struts has no interceptors, packages, IoC etc