View

As said in the introduction Jucas uses a Pull MVC Model and it is independent of both the template-engine and the page-flow mechanism. The only way that Jucas cares about the view is, that it gives the View access to all the components and that it provides a way that request-parameters (form inputs and url queries) can be mapped to properties and/or methods on components.

These two services are provided by the class org.jucas.ViewHelper and org.jucas.ActionCode. To understand this services a brief overview of the Request-Cycle Jucas uses is given and the usage of the two classes is explained.

Retrieving components by their names

Components in Jucas are normal Objects and in the view they can be used like normal Objects (by convention they are JavaBeans but Jucas does not enforce that). We assume that the usage of JavaBeans is known so we do not go deeper in how to use the components to get the data for rendering once they are gotten from Jucas.

Components are gotten from an instance of ViewHelper. For each request an instance of ViewHelper is put in the HttpServletRequest attributes under the name 'jucas'. The only way to get a ViewHelper is to get it from this attribute.

A ViewHelper is basically a wrapper around the BeanManager. The BeanManager is the component-manager of Jucas. This way it is possible to retrieve components from the ViewHelper through the BeanManager by their name.

Each Jucas component has a name. The name is an URI. The scheme of the URI specifies whether the component is a Service ('service') or the component is a JucasBean ('jucas'). The host of the URI specifies for Services the HiveMind id of the service (see Services) for JucasBeans it specifies the (full) Java class name of the JucasBean or the file from which the (xml definition of the Bean can be read).

The (optional) path and query part of the URI are ignored for Services. For JucasBeans they name the instance of the JucasBean. The (optional) fragment part of the URI is interpreted on both Services and JucasBeans as a nested BeanUtils properties path (including indexed and mapped properties). The URI than denotes the property specified by the path.

Some Examples

Component name URI

Description

service://org.foo.BusinessLogic

This will get the Service which is mapped in the module.xml file to the id org.foo.BusinessLogic. Services are shared in the web-app. For each service-id there is only one Service instance in the web-app.

service://org.foo.BusinessLogic/some/thing?here=go

This will return the samething as above (The path and query are ignored)

service://org.foo.BusinessLogic#articels

This will first get the Service as above and than the (JavaBean) property articels from this Service

service://org.foo.BusinessLogic#articels.item[someId].color(1)

This will first get the property from the Service as above and than from this the mapped property item is gotten and than from the item the indexed property color with the index 1 is gotten

jucas://org.foo.FooBean

This will get an instance of the class org.foo.FooBean. The instance id is the one which has no path and query in its name. Note if called again with the same URI and for the same Request, (and depending) Session the same instance is returned. If called in a different Session (and depending Request) a new Instance is returned.

jucas://org.foo.FooBean/some/other

Contrary to Services. This will get a second instance of org.foo.FooBean (the instance which has the instance id /some/other). As above if called again with the same URI the same instance is returned

jucas://org.foo.FooBean/some/other#someProp.otherProp

As we Services the fragment part is evaluated as a property path on the JucasBean as gotten from above

This names can be used in the method ViewHelper.getBean(String name) to get the component referenced by the name. If no component can be found (ie a Service is not defined, a JucasBean could not be instantiated for the given class, a property was not present etc) null is returned. A second method ViewHelper.getBeanEx(String name) throws in the case no component can be found an exception.

Which services can be used is described in hivemind module.xml files. JucasBeans are instantiated dynamically base on the host-part of the name. If for the given host path and query of the JucasBean name no JucasBean has been loaded yet in the current Request or Session (see next paragraph) a new instance is created and returned.

The scope in which components are held

Services are always held in Application scope and never in Session or Request. Of each Service there is only one instance which will live until the application ends. (under the hood they may be lazily loaded but this is invisible to the user).

JucasBeans can be either held in Session or Request scope, but never in Application. This has some implications (which generally do not apply to Services).

When during a Request a JucasBean with the same name is requested twice or more always the same instance is returned. If the JucasBean is held in Session the same instance is returned during the whole Session. If the JucasBean is held in Request a new instance is returned in each new Request.

Because JucasBeans are only held in Request or Session scopes. Two users (site-visitors) never share a JucasBean instance. Each has his own set of JucasBeans. To ensure this a view must never store a JucasBean in application scope or session scope. The best way to use JucasBeans is to retrieve them always newly from the ViewHelper.

Where the JucasBean is held is defined by the JucasBean itself. However the loader of the JucasBean (the View) can tell the JucasBean in which scope it whishes the bean to be held in. The JucasBean does not have to follow this wish but most do (If the JucasBean does not it should document this). To tell the wish to the JucasBean the methods ViewHelper.getBean(String name,String scope) and ViewHelper.getBeanEx(String name,String scope) are used. The value of the wished scope is either 'request' 'session' or null. Null means there is no wish, which is the same as calling getBean(String name) or getBeanEx(String name). For Services the wish is just ignored.

If a Bean is already held in Request and it is loaded again with the wish to be held in now session, and the JucasBean follows the wish to be held in session, the JucasBean is 'up-graded' that means it will be held from now on in Session. 'Down-grading' is not allowed. A JucasBean once in Session will never go back to Request. That means if a JucasBean is held in Session it will always be there. This asymmatry is there so that users can rely on components to be kept in session when it is loaded once in session and in the next reqeust the same instance will (always) be present. On the other hand if someone wants a component just in request he generally does not so much care if the next time the same instance is there. (If a component is realy dependent on always be kept in request once it is in request the JucasBean can always block the upgrade.)

Setting request-parameters on Peers

As said the view can specify which of its request-parameters are set as properties and/or are executed as methods on which Jucas components. However for security reasons and event-handling etc. The parameters can not be used on every component. Rather a component on which parameters can be set must implement the org.jucas.Peer interface.

Not only both Services and JucasBeans can implement this interface, but also normal Objects. If a normal Object implements Peer the object must be accesible through a BeanName. That is it must be accessible through a properties path given in the fragment of the name URI.

The interface Peer and its sub-interfaces define a number of methods of which is none important to the user of the Peer (They are only callbacks through which the Peer is informed of certain events during the actuall request-processing).

For the View the interface Peer is only a marker which denotes that request-parameters can be set on the Peer.

Further not all properties and methods defined by a Peer can be used to set Request-Parameters.

  1. for properties: the property name must end with Net. The property type must be String or String[]. The property may be indexed or mapped. The property must have a set method. A get method is no required (ie public void setNameNet(String name))

  2. for methods (actions): the method name must end with Net. The method must be public and return void. The method has either no arguments or any number of arguments of type String[]. The arguments must not be of any other type - also not String. (ie public void clearNet() or public void addItemNet(String[] names, String[] quantities)

Any number of properties can be set on each Peer during one request. But only one method may be called on each Peer per Request.

Request-Cycle

To understand how the mapping of request-parameters to properties and methods on Peers is done I want to first give a brief overview of the Request-Cycle of Jucas.

First a view defines which of its request-parameters should be set on which Peers. This is accomplished through an instance of org.jucas.ActionCode. A new instance is always gotten when the method ViewHelper.getActionCode() is called.

With the methods of the ActionCode the template than defines the mapping it wants to use. Out of this definition the ActionCode generates a token.

The token must than be set as the request-parameter 'jucas-action'. Generally this is done by sending the token as an html-hidden field or as a query-parameter. For both forms the ActionCode has convinience methods.

Than the html is send to the user. The user fills out the form or clicks a link (etc). And the request comes back to Jucas.

Jucas is implemented as a ServletFilter. If the filter recognizes the request-parameter jucas-action it will treat the other request-parameters. Otherwise it will just pass further. (The filter willin any case put the ViewHelper on the Request).

If the token is present the Filter parses it and based on the information in the token it retrieves the affected Peers from the BeanManager.

It than sets first the mapped properties on the Peers.

After this it executes the methods on the Peers. (Note: their can be only one method be called per peer per request)

Than the Peers are informed to update their state, the businesslogic etc and fire their events.

After the Model has been updated the Filter just give further in the FilterChain

The view can get the updated (or any other components) and the cycle starts again

Note this is not the full Request-Cycle. The actual Request-Cycle is more complicated because of security, validation, special-event-handling, logging etc. However for the basic understanding and how request-parameters are mapped to Peers its enough.

Mapping request-parameters to Peers with the ActionCode class

For each form and each URL to be send a new ActionCode has to be gotten from the ViewHelper through getActionCode().

To map a property use the method addProperty(String request-parameter-name, Peer toSetOn, String propertyName). The method will map the request-parameter to the property on the given Peer. The method also returns the request-parameter-name as given in this is used for convinience. A typical example would be:

   
 //for the property  AdressPeer.setNameNet(String name)
 //you would write in the html form
 
 <input type="text" value="someValue"
        name="actionCode.addProperty("name-para",adressPeer,"name"}">

Note that on Peers you can only set properties which end with Net. However you omit the Net suffix when you name the property in ActionCode (ActionCode will append if for you).

The second method wich can be used to map properties is ActionCode.addProperty(Peer toSetOn, String property). This method will do the same as the one before, but returns a either a new generated name (of the form jucas_ + an index) or it returns if the same property has already been set the name previously used.

To define an action which should be called on a Peer. You have to provide the Peer, the metod-name (without the Net suffix) and none, one or more parameters. The parameters can be of two types. (1) A String which is directly given as parameter to the method on the Peer or (2) like for properties the name of a request-parameter. In second case the parameter values are given as arguments to the Peer's method.

//for the method AdressPeer.clearNet()
${actionCode.getAction(adressPeer).setName("clear")}

//for the method OrderPeer.addItemsNet(String[] ids,String[] quantities)
//where both the ids and the quantities are read from a request-para
${actionCode.getAction(orderPeer).setName("addItems").addParameterArg("ids-para").addParameterArg("quant-para")}

//lets say you just wanted to set one item where you specify the id
//but the user specifies the quanitity:
${actionCode.getAction(orderPeer).setName("addItems").addStringArg("itemX").addParameterArg("quant-para")}

All the methods used for the action return either an empty string or an object with a toString() method which returns the empty String. So that they can be used with most expression-languages in templates.

After you have specified the mappings. The jucas-action token has to be given as a request parameter this is typically done in an html-hidden field or in the url-query part. For this ActionCode provides convinience methods. ActionCode.getHiddenField() will return a hidden field for this (the whole tag). ActionCode.getRequestPara() will return the a String of the form action-code=[url-encoded-token] this can be used in the query of the URL.

A last note: For each form in web-page and each url a new ActionCode should be gotten from ViewHelper.getActionCode(). And don't forget to print the action code, otherwise Jucas does not regard the request-parameters.