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: Controller: View: Config: Pet.java (simple bean) AddPet.java (WW action) addpet.jsp 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: 1. 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 2. 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 OGNL Samples OGNL Result pet.name getPet().getName() pet.toString() getPet().toString() pet.categories[0] First element of Categories collection name in {null,”fred”} True if name is null or “fred” categories.{name} Calls getName() on each Category in the collection, returning a new collection (projection) 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 UI Component Usage • 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") 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 Bundled Validators Validator Result RequiredField field != null RequiredString field != null && string.length() > 0 IntRange Integer in a given range DateRange Date in a given range Email Valid email field URL Valid URL field Expression / FieldExpression Any OGNL expression evaluates to true eg. pet.name != “dog” Allows you to create very powerful validations using just XML and your existing model 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): 1.Application 2.HTTP Session 3.HTTP Request 4.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 WebWork Cons • Not as well supported as Struts – No books, tool support etc (changing slowly) • Smaller community • No action pooling yet • Less standards support (eg JSTL & JSF) – JSF support might come when spec released – JSTL can be done by using tags • WebWork not part of Jakarta :) Want More? • http://www.opensymphony.com for website, mailing list, CVS etc • http://wiki.opensymphony.com for WW2/XW documentation • My blog - http://blogs.atlassian.com/rebelutionary • Email me - mike@atlassian.com • One chapter of my upcoming book on real world development with Java OSS technologies </shameless-plug> Thank you for listening - questions? Mike Cannon-Brookes ATLASSIAN - www.atlassian.com with XDoclet, JUnit,WebWork & Hibernate