280 likes | 401 Views
Idiomatic RSF – OTP, Messaging and BeanGuards. Antranig Basman, CARET, University of Cambridge. Recap of RSF Design. Three main threads On the design side, completely pure (X)HTML templates, previewability of appearance AND behaviour (IKAT renderer)
E N D
Idiomatic RSF – OTP, Messaging and BeanGuards Antranig Basman, CARET, University of Cambridge
Recap of RSF Design • Three main threads • On the design side, completely pure (X)HTML templates, previewability of appearance AND behaviour (IKAT renderer) • On the code side, a completely unintrusive binding to an application model (EL) • Portability of applications assured through request-scope IoC (RSAC) – based on the Spring framework • Statelessness and transparency of application behaviour through request cycle and ViewParameters • In between, a completely technology-neutral and behaviour-free representation of the *function* of a user interface (component tree)
Simple Rendering Sample Hello, <span rsf:id="current-user-name">Current User</span> UIOutput.make(tofill, "current-user-name", logic.getCurrentUserDisplayName()); • Uses an rsf:id on an HTML entity to show where to place the dynamic text • Does not have to be a span or div only! • UIOutput will send the escaped string to the id location in the component tree • UIBranchContainer and colon tags allow complex looping and branching without polluting the template
EL in RSF • VERY lightweight • Just a dot-separated path of bean names and properties (or 0-arg method for a method binding) • Binds component tree to the model without polluting it • No logic allowed • Example from CRUD tool – an input field from AddItemProducer.java: EL Reference Initial value Component class UIInput.make(addupdateitem,"item-title", "#{itemsBean.newItem.title}",DEFAULT_TITLE);
Quick recap of Spring • A Spring bean has no framework dependencies (in Spring, as in RSF) • Is configured in an XML file, which specifies bean class, bean properties and other dependencies (other beans) • Here is the itemsBean we referred to earlier: Bean name Injected Dependency <bean id="itemsBean" class="org.sakaiproject.crudplus.tool.ItemsBean" init-method="init"> <property name="logic" ref="org.sakaiproject.crudplus.logic.CrudPlusLogic" /> <property name="messages" ref=“targettedMessageList" /> </bean>
ItemsBean definition • Initial section of ItemsBean • Recall that our EL was #{itemsBean.newItem.title} • Classic example of the “captive entity pattern” • Halfway towards to OTP (which we will talk about NOW!) publicclass ItemsBean { public CrudPlusItem newItem=new CrudPlusItem(); public Map selectedIds=new HashMap(); private CrudPlusLogic logic; publicvoidsetLogic(CrudPlusLogic logic){ this.logic= logic; } ...
Reworked CrudPlusItem sample • Discuss the RSF inter-view messaging system based on TargettedMessageList • Streamline code with more advanced RSF features • Validation is packaged as reusable POJOs through “BeanGuards” • Also reworked to OTP (One True Path) ORM idiom • This idiom is less appropriate here than it would be in a completely “flat” model but still adds some value
I18N in RSF • Since 0.7.1, done primarily through the UIMessage component • Use UIMessage directly rather than UIOutput to render a localised message • Use UIMessage as a nested component in UICommand, UILink and Decorators to render a localised label • Use setMessageKeys() on a UISelect component to interpret label String array as message keys UIMessage.make(tofill,"level-header","modifytemplate.level.header", new String[]{currentLevel,new Integer(templateItemsList.size()).toString(),}); UIInternalLink.make(tofill,"modify_title_desc_link", UIMessage.make("modifytemplate.modify.title.desc.link"), new TemplateViewParameters(ModifyTemplateProducer.VIEW_ID, templateId));
Underlying I18N realities String result = MessageFormat.format( "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", arguments); • Messages are formatted as if by JDK’s standard MessageFormat (see Sun API) • Next layer is a Spring standard MessageSource (by default ReloadableResourceBundleMessageSource) • Top layer is RSF’s MessageLocator (application scope bean named messageLocator) which factorises off the dependence on the Locale and adds more convenience signatures • Finally there is the UIMessage component which allows access to the message bundle without injecting anything
TargettedMessageList • Functionality will not be fully rounded out until 0.8, but new in 0.7 is support for “info messages” • The “errors” scope is a kind of mini-flow scope, and is targetted ONLY at the following view • targettedMessageList is a request-scope (unreasonable) bean • Inject it where you want (also has a standard proxy) • Localisation, rendering, and formatting comes for free • Messages appear in the template at the tag marked rsf:id=“message-for:*” • Messages “widget” can be reskinned through messageTemplateContributor messages.addMessage(new TargettedMessage("item_updated", new Object[] {item.getTitle()}, TargettedMessage.SEVERITY_INFO));
More on scr= I • SCRs are provided for HTML <head> aggregation in reusable components • scr=contribute-script and scr=contribute-style mark CSS and JS declarations that need to be aggregated from components • scr=head-collect marks the place they should be collected (automatically inferred) • scr=portal-matter marks some <head> material that is a placeholder for portal definitions and should be replaced (in progress)
More on scr= II • Automatic inference of SCR tags is done by a TemplateParseInterceptor • Any manual scr= declared in the template will block automatic inference publicinterface TemplateParseInterceptor { publicvoidadjustAttributes (String tag, Map attributes); }
BeanGuards™ for Validation • A declarative strategy for attaching rules to modifications of the bean container • Similar to, for example, Spring’s declarative transaction demarcation, or the Spring Acegi security framework • Could also be seen as a kind of AOP • Although in this case the kind of AOP you can eat between meals without losing your appetite • In any case, the key functionality is to provide guaranteed execution of logic without intruding either on the target, or on the validation code with framework code
BeanGuards • Two main styles of BeanGuards • The Spring Validator interface is supported directly • Also supports pure POJO validators (preferred if Spring portability is no concern) • Both of these are simply declared as beans (typically at application scope) *@authorRodJohnson publicinterface Validator { booleansupports(Class clazz); voidvalidate(Object obj, Errors errors); }
Declaring a BeanGuard • As well as declaring the Guard itself, you must declare WHAT it is meant to be guarding • A Guard is targetted at a particular EL path within the container • Many options for the “style” of the Guard, but typically one guards write access to the target path • RSF contexts provide the helpful parent definition writeGuardParent • For Spring Validator, you can use an inner bean for an all-in-one definition: <bean id="priceIncreaseGuard" parent="writeGuardParent"> <property name="guardedPath" value="priceIncrease"/> <property name="guard"> <bean class="uk.org.ponder.rsf.springmvcstep.bus.PriceIncreaseValidator"/> </property> </bean>
Using a POJO Validator • For a POJO validator, the validator must be declared as a request scope bean • Need to specify the property on the validator to receive the target object – can be combined with validator bean name to form an EL-like expression <bean parent="writeGuardParent"> <property name="guardedPath" value="CrudPlusItem.*" /> <property name="guardProperty“ value="CrudPlusItemValidator.item" /> </bean> At request scope: <bean id="CrudPlusItemValidator" class="org.sakaiproject.crudplus.tool.CrudPlusItemValidator" /> In code: publicclass CrudPlusItemValidator { publicvoidsetItem(CrudPlusItem newItem){ if(newItem.getTitle()==null|| newItem.getTitle().equals("")){ thrownew IllegalArgumentException("title_required"); } } }
POJO Validators vs. Spring Validators • Spring Validators • Are a somewhat recognised standard • Can be implemented in application scope • Cannot validate something which is null! • POJO Validators • Can use the “sleaze” style of throwing a message key as exception text, and in that case be completely dependency free • Can be written without casts • Would need to inject TargettedMessageList if you wanted more complex message formatting
One True Path • RSF ORM “idiom” rather than “library” • OTP (= “One True Path”) assigns unique EL path to each entity of data model • RSF “EL” is slimmed down so much (only elementary reads/writes permitted) that it is safe to address arbitrarily nasty “POJO” models, even those managed by Hibernate • In some cases can lead to the abolition of “DAO”s • Is an idea of more general power (come to “Helpers BOF”)
How does RSF OTP work? • Assigns a unique EL path to each Entity managed by ORM, where read/write of the EL is *directly* bound to read/write of the model • The EL path for the CrudPlusItem with ID of 5 is #{CrudPlusItem.5} • Add new entities with the special id form #{CrudPlusItem.new 1} Following two points are part of “Full” OTP but are not used in the OTP version of CrudPlusTool • Issue special binding type (UIDeletionBinding) to “unlink” or “delete” an existing entity • If the request concludes normally, commit – if any kind of exception propagates out, rollback. • Not necessarily tied to any particular flavour or even use of ORM – EL paths are completely “generic”
How to set up OTP • Again, a general concept rather than a specific library/implementation • Using RSFHibernate exposes the entire entity set automatically via OTP • SakaiRSF contains GenericDAOEntityBeanManager which will automatically convert entities managed by Aaron Zeckoski’s Generic DAO to OTP • In general PonderUtilCore contains BasicObstinateEBL (implementing ObstinateEntityBeanLocator) which will do most of the legwork in setting up an OTP mapping • You can just do it yourself! (Can use plain Maps rather than BeanLocators)
How to use OTP • In “pure” OTP, you can just emit bindings from the producer, and do without any kind of backing bean at all (see this style in OTP version of Vancouver’s TaskList app) • In CrudPlusItem, only makes sense to operate OTP for “add/update” operations UIInput.make(newtask,"new-task-name","#{Task.new 1.task}"); UICommand.make(newtask,"submit-new-task",null); newtask.parameters.add( new UIELBinding("#{Task.new 1.owner}",userId)); newtask.parameters.add( new UIELBinding("#{Task.new 1.siteId}",siteId)); ... deleteform.parameters.add(new UIDeletionBinding("#{Task}", new ELReference("#{deleteIds}"))); if(logic.canWriteItem(item)){// check for current user and site UIBoundBoolean.make(itemrow,"select-item", "itemsBean.selectedIds."+ item.getId(), Boolean.FALSE);
When to use OTP • OTP comes into its own more and more in apps with larger schemas, with more anaemic models • Is also much less trouble in single-ClassLoader environments • Is particularly powerful for auto-generated apps (XML ViewProducers, hbm2java models) • Since CRUDPlus has *some* logic (largely permissions checking) in its logic layer, requires more work • Need to set up a “Front DAO” – CrudPlusLogicDAO • I still think the cost is just about worth it in this case • “Changed value logic” is much clearer • With bigger entities would save more and more time
The Magic Ingredient publicvoidinvokeTransactionalAccess(Runnable toinvoke){ toinvoke.run(); } • What is this method in GenericDAO for? • “Natural” OTP semantics are Spring transactional semantics • Request concludes normally == commit transaction • Exception is thrown == perform rollback • RunnableInvoker is the greatest interface that was never in the JDK • In other languages (Javascript, Ruby, any of the functional family is not even necessary)
The Great OSIV vs. DTO debate • OSIV = Open Session In View • DTO = Data Transfer Object • Both are answers to the architectural risk posed by Hibernate • OSIV costs are of leakage • Many believe that the view layer should be susceptible to raw persistence exceptions • In fact RSF has a coherent exception-handling strategy and this is not such a serious problem • DTO costs are of redundancy • Is an absolute, rather than a relative architectural cost (i.e. is unconditionally bad! :) ) • This debate is somewhat aligned to the Anaemic vs. Rich Domain model debate (backwards – a DTO is a fabricated Anaemic model) • ONE purpose of invokeTransactionAccess is to enable OSIV
Why have I never seen this before? • Is the natural completion of Sakai’s “poor man’s Enterprise Container” idiom • Currently obscured by the fact that Sakai users are “happy” to have Hibernate as a global ClassLoader visible dependency • Ultimately, both Spring and Hibernate will GO • invokeTransactionalAccess in all frameworks enables OSIV • in RSF OSIV is considerably less messy and more controllable – can simply target the “alteration” cycle via a RunnableInvoker • In RSF iTA also enables full OTP semantics, again since it properly only targets the alteration cycle publicclass GenericDAORunnableInvoker implements RunnableInvoker { private CoreGenericDao genericDAO; publicvoidsetGenericDAO(CoreGenericDao genericDAO){ this.genericDAO= genericDAO; } publicvoidinvokeRunnable(Runnable torun){ genericDAO.invokeTransactionalAccess(torun); } }
A RunnableInvoker Conversation • Nig: “Don’t you think people will be upset by the fact we don’t supply JTA like all the other containers? • Thorton: “Actually I think all the other containers use JTA because they can’t think up RunnableInvoker” RIP Andrew Thornton, RSAC instigator and general RSF curmudgeon (No, he’s not really dead!) • RunnableInvoker/iTA is the natural conclusion of Spring • Transactional thinking, and allows transaction semantics to be • exported out of an API into another ClassLoader with ZERO • code intrusion • Needs to be fleshed out with partners InvokeModifyingAccess • and invokeReadOnlyAccess
RunnableInvoker put to more work • AOP fanatics will recognise RunnableInvoker as the natural body of an “around” advice • Can also be used as a BeanGuard with the AROUND timing • One use: converting exceptions thrown by JDK conversion methods into suitable “sleaze” forms – see uk.org.ponder.util.StaticExceptionConvertingRI • Can also be used as the body of a BeanFetchBracketer • Like a BeanGuard only stronger – brackets the fetch of ANY RSAC bean • Example: RSF LogonTest sample, supplies a static declarative strategy for protecting pages from being viewed by a user who is not logged on
LogonTest App SecurityWrapper publicclass SecurityWrapper implements RunnableInvoker { private ViewParameters viewparams; private LogonBean logonbean; publicvoidsetViewParameters(ViewParameters viewparams){ this.viewparams= viewparams; } publicvoidsetLogonBean(LogonBean logonbean){ this.logonbean= logonbean; } publicvoidinvokeRunnable(Runnable towrap){ if(logonbean.name==null &&!(LogonProducer.VIEW_ID.equals(viewparams.viewID))){ thrownew SecurityException("Cannot view page "+viewparams.viewID +" while not logged on"); } towrap.run(); } } <bean parent="alterationWrapperParent"> <property name="value"> <bean parent="RSACBridgeProxy"> <property name="targetBeanName" value="securityWrapper" /> </bean> </property> </bean>