Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Case Study: The Next Generation Online Travel Platform

Mark Meeker
December 15, 2007

Case Study: The Next Generation Online Travel Platform

Spring Experience - December, 2007

Mark Meeker

December 15, 2007
Tweet

More Decks by Mark Meeker

Other Decks in Technology

Transcript

  1. Agenda • Project context • Technology architecture • Building UI

    forms • Interesting Flows • Security and SSL • Progressive Enhancement • Composite View Pattern • Componentization • URLs and REST • State management and HTTP browser caching headers • Internationalization • Performance and concurrency • Summary • Q&A
  2. Direct Connections Booking Services Travel Business Services Thin Presentation Layer

    (Tomcat, Spring, SWF) Distributed Business Session Cache Distributed Web Session Cache
  3. Dear Java Developers, Whatever you do, don’t choose a web

    framework that generates its own XHTML (or worse, non- validating @#$% HTML that we wouldn’t be caught dead near). Your friends, The UI Team
  4. Form Custom Tags • Started pre- spring-form.tld • Full control

    of mark-up • Abstracts concept of binding • Limit attributes available to be set • type, maxlength, class, autocomplete • Extended scope to include <label />
  5. Form Custom Tags • Additional values for inputs • label-key,

    label-state, required, read-only • Build in hook for JavaScript • Turn-off running against live model • Augment error handling
  6. Error Display • Custom Error custom tag • Formats errors

    with correct styles • Support for error tag debugging • Error highlighting built into form inputs
  7. Interesting Flows • Leverage framework for page navigation • Large

    number of special conditions in booking path • Informational pop-up flows need data from flow, but don’t affect it • “Helper” flows for RESTful Ajax calls • Requirement for a “wait page”
  8. Security and SSL • Browser security alerts are bad for

    business! • HTTP vs. HTTPS based on URL pattern • Spring Security channel filter to enforce SSL • Custom link tag insures correct protocol • Single source of truth
  9. Flow listener with flow annotations to indicate flow’s security level

    <action-state id=“purchase”> <attribute name=“permissions” value=“USER”/> … </action-state>
  10. Universality • Serve pages that will work on any and

    every device • Fundamental basis for the web • Goes hand-in-hand with Accessibility
  11. Progressive Enhancement • Separation of Layers ( HTML / CSS

    / JS ) • Phased development • Easier to define view model and flows • And the “good stuff” too!
  12. Progressive Enhancement 1 2 3 4 HTML CSS JS 

              
  13. <img src=”/map/image” /> <ul> <li><a href=”/map/move?dir=n”>North</a></li> <li><a href=”/map/move?dir=e”>East</a></li> <li><a href=”/map/move?dir=s”>South</a></li>

    <li><a href=”/map/move?dir=w”>West</a></li> ... </ul> <ul> <li><a href=”/map/zoom?level=1”>Zoom Level 1</a></li> <li><a href=”/map/zoom?level=2”>Zoom Level 2</a></li> <li><a href=”/map/zoom?level=3”>Zoom Level 3</a></li> ... </ul>
  14. XHR

  15. Hijax • Term coined by Jeremy Keith • Bulletproof Ajax

    (http://bulletproofajax.com/) • Pull in portion of page via Ajax when XHR is supported • Re-use same portion when a full page refresh is required • Requires UI Componentization
  16. Header Footer Rail Module 1 Login Module Module 2 JS-Enabled

    XHR noscript page transition Login Module
  17. • Separates “layout” of page from content • Allows to

    plug in different modules into page • Used in Apache Tiles • Leverage in-house framework • Try and gain as much re-use of JSP code Composite View Pattern
  18. Reusable Component • View Module (JSP) • Model • Validator

    [optional] • Executor (Component Action) [optional] • Tests
  19. public class ShipperSelection implements Serializable { private List<ShipperOption> shipperOptions; private

    ShipperOption shipperOption; private COUNTRY country; public COUNTRY getCountry() { return country; } public void setCountry(COUNTRY country) { this.country = country; } public ShipperOption getShipperOption() { return shipperOption; } public void setShipperOption(ShipperOption shipperOption) { this.shipperOption = shipperOption; } public List<ShipperOption> getShipperOptions() { return shipperOptions; } public void setShipperOptions(List<ShipperOption> shipperOptions) { this.shipperOptions = shipperOptions; } public void addShipperOption(ShipperOption shipperOption) { this.shipperOptions.add(shipperOption); } }
  20. /** * @spring.bean id="shipperSelectionExecutor" * */ public class ShipperSelectionExecutor {

    private static PRODUCT_TYPE[] productTypes = {PRODUCT_TYPE.AIR}; public PRODUCT_TYPE[] getApplicableProductTypes() { return productTypes; } public boolean supports(Class clazz) { return ClassUtils.isAssignable(ShipperSelection.class, clazz); } public void setupBookComponent(ShipperSelection shipperSelection, BookState newParam) { List<ShipperOption> shipperOptions = getShipperOptions(); shipperSelection.setShipperOptions(shipperOptions); } private List<ShipperOption> getShipperOptions() { … return shipperOptions; } }
  21. /** * @spring.bean id="shipperSelectionValidator" * */ public class ShipperSelectionValidator implements

    Validator { private List<COUNTRY> restrictedCountries; public ShipperSelectionValidator(List<COUNTRY> restrictedCountries) { this.restrictedCountries = restrictedCountries; } public boolean supports(Class clazz) { return ClassUtils.isAssignable(ShipperSelection.class, clazz); } public void validate(Object obj, Errors errors) { ShipperSelection c = (ShipperSelection) obj; if (restrictedCountries.contains(c.getCountry()) { errors.rejectValue("country", "1535"); } } }
  22. <bean id="shipperSelectionExecutor" class="com.orbitz.wl.web.book.air.action.ShipperSelectionExecutor" /> <bean id="shipperSelectionValidator" class="com.orbitz.wl.web.book.air.validator.ShipperSelectionValidator" /> <bean id="shipperSelectionComponent"

    class="com.orbitz.webframework.component.ComponentFactoryBean"> <property name="singleton" value="false" /> <property name="id" value="WL_UBP310.110" /> <property name="name" value="shipperSelection" /> <property name="modelClass" value="com.orbitz.wl.web.book.air.model.ShipperSelection" /> <property name="executor" ref="shipperSelectionExecutor"/> <property name=”validator” ref=”shipperSelectionValidator” /> </bean>
  23. /** * @spring.bean id="travelerInfoAction" * @spring.property name="formObjectName" value="travelerInfoForm" * @spring.property

    name="formObjectClass" *value="com.orbitz.webframework.component.Form" * @spring.property name="formObjectScope" value="FLOW" * @spring.property name="validator" ref="formValidator" * @spring.property name="propertyEditorRegistrators" ref="propertyEditorRegistrators" */ public class TravelerInfoAction extends Action { private static final String SHIPPER_SELECTION = "shipperSelection"; private static final String TRAVELERS_INPUT = "travelersInput"; @Override protected String[] getComponentIds() { return new String[] { SHIPPER_SELECTION + "Component", TRAVELERS_INPUT + "Component",}; } @Override public Event setupComponents(RequestContext context) { ShipperSelectionExecutor flightTicketExecutor = getComponentExecutor(context, SHIPPER_SELECTION); flightTicketExecutor.setupComponent(shipperSelection.getModel(), getBookState(context)); return success(); } public Event postProcess(RequestContext context) { TravelersInput travelersInput = getComponentModel(context, TRAVELERS_INPUT); TravelersInputExecutor whosTravelingExecutor = getComponentExecutor(context, TRAVELERS_INPUT); whosTravelingExecutor.executePostProcess(travelersInput, getBookState(context)); return success(); } }
  24. public interface Action { public Event execute(RequestContext context) throws Exception;

    } public interface RequestContext { public MutableAttributeMap getRequestScope(); public MutableAttributeMap getFlashScope(); public MutableAttributeMap getFlowScope(); public MutableAttributeMap getConversationScope(); … } requestContext.getExternalContext().getSessionMap() Session scope obfuscated
  25. no view state no pause no continuation enforce with customization

    at XMLFlowBuilder level & made easier by SWF-310
  26. Internet Explorer’s URL limit: 2083 characters source: Source: http://support.microsoft.com/kb/208427 http://www.ebookers.com/shop/home?

    type=air&ar.type=roundTrip&ar.rt.leaveSlice.orig.key=L HR&ar.rt.leaveSlice.dest.key=FLL&ar.rt.leaveSlice.date =12%2F12%2F07&ar.rt.leaveSlice.time=Anytime&ar.rt.retu rnSlice.date=15%2F12%2F07&ar.rt.returnSlice.time=Anyti me&ar.rt.numAdult=1&ar.rt.numSenior=0&ar.rt.numChild=0 &ar.rt.child%5B0%5D=&ar.rt.child%5B1%5D=&ar.rt.child %5B2%5D=&ar.rt.child%5B3%5D=&ar.rt.child %5B4%5D=&ar.rt.child%5B5%5D=&ar.rt.child %5B6%5D=&ar.rt.child%5B7%5D=&search=Search+Flights
  27. <flow …> … <subflow-state id=”executeSearch” flow=”search”/> <attribute-mapper> <input-mapper> <input-attribute name=”dealsModel.searchAttr1”/>

    <input-attribute name=”dealsModel.searchAttr2”/> </input-mapper> </attribute-mapper> <transition on=”someOutcome” to=”someView”/> </subflow-state> </flow> AttributeMapper config
  28. SWF-297 will also allow the input-mapper to also record validation

    error messages, providing a better alternative to the FormAction generally used for this purpose now.
  29. /** * Prevent the response from being cached. * See

    www.mnot.net.cache docs. */ protected final void preventCaching(HttpServletResponse response) { response.setHeader(HEADER_PRAGMA, "No-cache"); if (this.useExpiresHeader) { // HTTP 1.0 header response.setDateHeader(HEADER_EXPIRES, 1L); } if (this.useCacheControlHeader) { // HTTP 1.1 header: "no-cache" is the standard value, // "no-store" is necessary to prevent caching on FireFox response.setHeader(HEADER_CACHE_CONTROL, "no-cache"); response.addHeader(HEADER_CACHE_CONTROL, "no-store"); } } Spring source when cacheSeconds == 0
  30. editors.put(Currency.class, new CurrencyEditor()); editors.put(Scalar.class, new ScalarEditor()); editors.put(DateTime.class, new JodaDateTimeEditor(true)); editors.put(YearMonthDay.class,

    new JodaYearMonthDayEditor(true)); editors.put(LocalDate.class, new JodaLocalDateEditor(true)); … register PropertyEditors
  31. "http-8585-Processor39" daemon prio=1 tid=0x081634c8 nid=0x65b7 waiting for moni tor entry

    [0x9d41a000..0x9d41e0b0] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistr y.getSingleton(DefaultSingletonBeanRegistry.java:114) - waiting to lock <0xb0d85828> (a java.util.LinkedHashMap) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean (AbstractBeanFactory.java:187) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean (AbstractBeanFactory.java:156) lock contention
  32. Other lock contentions • JDK RMIClassLoaderSpi • JDK java.security.* •

    Tomcat • 3000 tag invocations in search results page view • Log4j • ICU
  33. Summary • Project context • Technology architecture • Building UI

    forms • Managing Flows • Security and SSL • Progressive Enhancement • Composite View Pattern • Componentization • URLs and REST • State management and HTTP browser caching headers • Internationalization • Performance and concurrency • Summary • Q&A