# Web addon # Introduction Web add-ons are standard war files. The files included in the war file will be copied onto Soffid console war file. In order to keep compatibility between add-ons, it's forbidden to overwrite existing file. Instead, the war file can contain xslt files to modify existing xml ones. As an example, the following menu.zul.xsl allows the identity federation add-on to create a menu entry on menu.zul file: ```XML ``` There is an special file named iam-label.properties. It contains the console localized labels, accessible via ${c:l EL expression or Labels class. This file, and all its localized versions will be merged automatically with existing iam-label.properties to create a new one. # zkdb - ZK Data binding tool ## Introduction zkdb is a set of Java classes and ZK components designed to easy the development of web applications driven by a data model. ZKIB aims to fill the gap between the user interface defined in ZK and logic stored mainly in EJBs. ZKIB basic components are:
- A data model, known also as DataSource. - ZK components capable of obtaining the value of simple attributes or objects belonging to the DataSource - data model manipulation API, based on XPath.
# Developers guide Developers guide # Creating a user interface data model There are three alternative ways to implement user interface data model. The first one is to retrieve information from an XML file. Its use is simple and easy to implement during user interface prototyping phase. A second alternative is to create a set of glue classes, similar to a DAO object, that are responsible for retrieving user interface data from the storage system, and save changes back. Finally, there is a way to get this glue layer in a declarative way, by means of an XML behavior descriptor. As an example, a let's build a web page for querying countries and cities, the data will be modeled by the following XML model: ```XML <country name = "Spain" abbreviation = "en"> <city name = "Palma de Mallorca" /> <city name = "Madrid" /> </country> <country name = "USA" abbreviation = "us"> <city name = "Wasington" /> </country> <country name = "Deutschland" abbreviation = "on"> <city name = "Berlin" /> <city name = "Bonn" /> <city name = "Hamburg" /> </country> </my-data> ``` After creating the data model, you can make use of it from any zul page including a xmlDataSource component: ```XML <?xml version="1.0" encoding="UTF-8"> <zk> <xmlDataSource id="mydata" src="/my-data.xml" /> </zk> ``` # Binding data model simple values to zk components <span class="notranslate">In the previous section, we saw how to declare a sample data model.</span> <span class="notranslate">To bind a component to a data model element, the bind attribute can be used. </span><span class="notranslate">The bind attribute value must be composed of the data source path to be used, a colon separator, and the xPath element to the data model element.</span> <span class="notranslate">The data source is selected based on the zk path.</span> Both simple paths<span class="notranslate"> (/model1) or full paths (//page1/window1/window2/model2).</span> <span class="notranslate">The data model element is specified using its XPath.</span> <span class="notranslate">Internally, it's using an Apache commons JXPath version.</span> <span class="notranslate">So, in order to display a label with the value of the "name" attribute </span>of the XML document "title" entity, set the bind attribute of a new label the value "/mydata:/title/@name", as shown in the following example: ```XML <?xml version = "1.0" encoding = "UTF-8"> <zk> <xmlDataSource id = "mydata" src = "/my-data.xml" /> <label bind = "/mydata:/title/@name" /> </zk> ``` <span class="notranslate">Mind that this binding is only one way, because the label object does not allow the user to change its content. When using a textbox, checkbox or any other input element, the binding will be bidirectional, </span><span class="notranslate">allowing the user to change the associated DOM XML file.</span> <span class="notranslate">Anyway, the changes will not be serialized to the original XML file.</span> # Binding data model context <span class="notranslate">To easiest the readability and maintainability of the code, and to shorten in XPath paths, some components can works as a relative xpath context for the contained ZK components.</span> <span class="notranslate">When a container type object is associated with a data model element, binds on child components that do not specify a data source are understood as parent container relative XPaths.</span> <span class="notranslate">The components that can act as context data model implement the es.caib.zkib.BindContext interface.</span> <span class="notranslate">Currently, grid, lisbox, row and form components.</span> Due to implementation<span class="notranslate"> constraints, the listiem object does not implement the aforementioned interface.</span> <span class="notranslate">The simplest of those components is the Form object (which derives from Vbox).</span> <span class="notranslate">For context associations, the attribute to be used is called datapath, as shown in the following example:</span> ```western <?xml version = "1.0" encoding = "UTF-8"> <zk> <xmlDataSource id = "mydata" src = "/ my-data.xml" /> <form dataPath = "/mydata:/title"> <label bind = "/@name" /> <textbox bind = "/@name" /> <button label = "Update" /> </form> </zk> ``` In this example, both the object label, as the textbox object refer to the same attribute @name, which is relative to the context /mydate:/title. When the user changes the textboxitem, the same text will be shown on the label. Mind the synchronization will only be done when the data is sent to the server, by means of any ZK event. In order to make user interface to be more responsive, simply add an onChange or onChanging null handler to the textbox component. ```XML <?xml version = "1.0" encoding = "UTF-8"> <zk> <xmlDataSource id = "mydata" src = "/ my-data.xml" /> <form dataPath = "/mydata:/title"> <label bind = "/@name" /> <textbox bind = "/@name" onChanging=""/> <button label = "Update" /> </form> </zk> ``` # Binding Data Model Collections <span class="notranslate">Components of type listbox and grid behave as complex containers of object collections.</span> <span class="notranslate">These components can be assigned a Xpath expression that does not identify a single object, but a collection of objects or values.</span> <span class="notranslate">In this case, the system will generate a row for each of the values retrieved by the XPath query.</span> <span class="notranslate">In order to define how to render listbox elements (listiem) bound to each data model row, the dataitem component should be used. The dataitem component is used as a template for the generation of any listitems.</span> <span class="notranslate">It should be noted that the each generated listitem is bound to a single object from the data model collection. This object is used also as a data context for the listcell's bound xPath. More and more, the attribute bind on the dataitem is the XPath that point to the value of the listitem.</span> <span class="notranslate">In a similar way, in order to define how a grid row should be rendered, the datarow component should be used. It is used as a template for the actual rows generated. A row will be generated for each single element obtained from the data model collection. This object is bound to it's corresponding row and serves as the data context for the row children components.</span> <span class="notranslate">So, the following example illustrates how to show the countries on a list component:</span> ```XML <?xml version="1.0" encoding="UTF-8"> <?page title="ZK Data binding Demo"> <zk> <xmldatasource id="mydata" src ="/my-data.xml" /> <vbox> <form dataPath = "/mydata:/title"> <label bind="/@name" /> <textbox bind = "/@name" /> <button label = "Update" /> </form> <listbox dataPath = "/country:/mydata"> <listhead> <listheader label="Abbr" /> <listheader label="Name" /> </listhead> <dataitem bind = "/@abbreviation"> <listcell bind = "/@abbreviation" /> <listcell bind = "/@name" /> </dataitem> </listbox> </vbox> </zk> ``` <span class="notranslate">Note that the dataitem component has a dual behavior regarding the data model. On one side, it serves as a context to the inner object, and by the other side, it is assigned a value of the data model, according to the specification of the listitem component.</span> # Components as a data source <span class="notranslate">The listbox component has a dual role, as a data consumer and as a data source. We've seen how listbox component can act as a data consumer in the previous pages. No we'll see how is it acting as a data source.</span> <span class="notranslate">Any zul component can use the listbox path as the left handside member of a bind expression. In such a case, the result would be the same than binding such components to the xpath relative to the listitem currently selected at the list box. When no listbox element is selected, the resulting XPath will be void, and thus the component will be disabled. When the selected listitem changes, automatically the listbox bound components will c</span> <span class="notranslate">As an example, the following code will show the name of the selected country, allowing the user to modify it.</span> ```XML <?xml version="1.0" encoding="UTF-8"?> <?page title="ZKIB Demo" ?> <zk> <xmldatasource id="mydata" src="/my-data.xml" /> <vbox> <form dataPath="/mydata:/title"> <label bind="/@name"/> <textbox bind="/@name"/> </form> <listbox id="countries" dataPath="/mydata:/country"> <listhead> <listheader label="Abbr"/> <listheader label="Name"/> </listhead> <dataitem bind="/@abbreviation"> <listcell bind="/@abbreviation" /> <listcell bind="/@name"/> </dataitem> </listbox> <hbox> <label value="Active country:"/> <textbox bind="/countries:/@name"/> </hbox> <button label="Update"/> </vbox> </zk> ``` Additionally, you can display a grid with the names of the cities of the selected country. Using the previous listbox as the data source, the component synchronization is automatic: ```XML <?xml version="1.0" encoding="UTF-8"?> <?page title="ZKIB Demo" ?> <zk> <xmldatasource id="mydata" src="/my-data.xml" /> <vbox> <form dataPath="/mydata:/title"> <label bind="@name"/> <textbox bind="@name"/> </form> <listbox id="countries" dataPath="/mydata:/country"> <listhead> <listheader label="Abbr" /> <listheader label="Name"/> </listhead> <dataitem bind="@abbreviation"> <listcell bind="@abbreviation" /> <listcell bind="@name" /> </dataitem> </listbox> <hbox> <label value="Active country:"/> <textbox bind="/countries:/@name"/> </hbox> <grid dataPath="/countries:/city"> <columns> <column label="City"/> </columns> <datarow> <label bind="@name" /> </datarow> </grid> <button label="Update"/> </vbox> </zk> ``` # Data model manipulation The data model can be manipulated directly using the JXPathContext interface or indirectly through components, Whenever the user changes the contents of a ZK component, which is bound to a data model object, the change is propagated to the model, which in turn, propagates it to any ZK component that is bound to the modified data model object. <span class="notranslate">To ease data manipulation, the listbox object, has two new methods: addNew and delete.</span> <span class="notranslate">The first one creates a new object in the data model, and therefore in the listbox, while the second one removes it from the list and the model.</span> <span class="notranslate">Additionally, the autocommit attribute determines whether listbox will try to perform a commit the data model each time the user changes the selected item.</span> <span class="notranslate">Alternatively, the data model can be directly manipulated through a Apache's commons-JXPath derived package. </span>Every d<span class="notranslate">ata source component (including datamodel and lisbox) contains a JXPathContext.</span> <span class="notranslate">Through this context JXPath invocations can be made, usign getValue, setValue, removePath or createPath methods.</span> <span class="notranslate">For more information about JXPath use, please review the available docs at: [http://jakarta.apache.org/commons/jxpath/](http://translate.google.com/translate?hl=es&prev=_t&sl=es&tl=en&u=http://jakarta.apache.org/commons/jxpath/)</span> <span class="notranslate">Mind that the components will be notified of any change made through JXPath API, but the won't be noticed of any change made directly to the underlying data model. On the undesirable case underlying data objects are modified directly, you can force the components to be notified by getting a Pointer to the modified object and calling the invalidate method on it. </span> As an example, the following example shows how to delete a city or ad a new one to the data model: ```western <?xml version="1.0" encoding="UTF-8"?> <?page title="ZKIB Demo" ?> <zk> <xmldatasource id="mydata" src="/my-data.xml" /> <vbox> <form dataPath="/mydata:/title"> <label bind="@name"/> <textbox bind="@name"/> </form> <listbox id="countries" dataPath="/mydata:/country"> <listhead> <listheader label="Abbr" /> <listheader label="Name"/> </listhead> <dataitem bind="@abbreviation"> <listcell bind="@abbreviation" /> <listcell bind="@name" /> </dataitem> </listbox> <hbox> <label value="Active country:"/> <textbox bind="/countries:/@name"/> </hbox> <grid dataPath="/countries:/city"> <columns> <column label="City"/> <column/> </columns> <datarow> <label bind="@name" /> <image src="/img/remove.gif"> <attribute name="onClick"> row = self.parent; ctx = row.dataSource.jXPathContext; ctx.removePath(row.xPath); ctx.getPointer("/").invalidate(); </attribute> </image> </datarow> </grid> <button label="Add City"> <attribute name="onClick"> ctx = countries.jXPathContext; pointer = ctx.createPath("/city[count(/city)+1]"); ctx2 = ctx.getRelativeContext(pointer); ctx2.createPath("/@name").setValue("London"); ctx.getPointer("/").invalidate(); </attribute> </button> </vbox> </zk> ``` # Building dynamic data model While building the data model using XML files is possible, it's advisable to use more dynamic data models in the production environment. Alternatively, ZKIB module provides a set of classes that ease interaction with data models based on JDBC databases and object-oriented layers like Hibernate or EJB. Additionally. To make use of these dynamic data models, the developer must implement one or more instances of the DataNode object. These DataNode objects act as wrapping around the actual business bean, managing the persistence layer. In general, you should define a class for each XML file equivalent entity. Thus, in the previous example, the developer should implement classes for the root (my-data), title, country and city objects. In some cases you won't need to develop a new DataNode class as long as the data object needs not to be persisted. For those simple data objects, a SimpleDataNode object can be used. Each class is responsible for encapsulating a business object, and for its serialization to the persistent repository, usually a database. Also, each class is responsible for retrieving child objects. The children population function is performed by the finder interface. A DataNode object must implement the following methods: <div id="bkmrk-method-attributes-de"><div><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><colgroup><col></col><col></col><col></col></colgroup><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Method: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0">**<span class="notranslate">Method</span>** </th><th aria-disabled="false" aria-label="Attributes: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0">**<span class="notranslate">Attributes</span>** </th><th aria-disabled="false" aria-label="Description: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="2" role="columnheader" scope="col" tabindex="0">**<span class="notranslate">Description</span>** </th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd"><span class="notranslate"><constructor></span> </td><td class="confluenceTd"><span class="notranslate">DataContext</span> </td><td class="confluenceTd"><span class="notranslate">Optionally, you must declare the "finders", by calling the method addFinder</span> </td></tr><tr role="row"><td class="confluenceTd"><span class="notranslate">doInsert</span> </td><td class="confluenceTd"></td><td class="confluenceTd"><span class="notranslate">Insert a new object in the persistent storage</span> </td></tr><tr role="row"><td class="confluenceTd"><span class="notranslate">doUpdate</span> </td><td class="confluenceTd"></td><td class="confluenceTd"><span class="notranslate">Update an object in the storage persitente</span> </td></tr><tr role="row"><td class="confluenceTd"><span class="notranslate">doDelete</span> </td><td class="confluenceTd"></td><td class="confluenceTd"><span class="notranslate">Update an object in the storage persitente</span> </td></tr></tbody></table> </div></div><span class="notranslate">It's noticeable, that doInsert, doDelete and doUpdate methods are always invoked in a transactional context, coordinated through the commit method of the DataSource.</span> <span class="notranslate">The DataNode derived objects can access the data of the underlying business object by calling the inherited method getInstance.</span> <span class="notranslate">The Finder interface must implement the following methods:</span> <div id="bkmrk-method-result-descri"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><colgroup><col></col><col></col><col></col></colgroup><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Method: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0">**<span class="notranslate">Method</span>** </th><th aria-disabled="false" aria-label="Result: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0">**<span class="notranslate">Result</span>** </th><th aria-disabled="false" aria-label="Description: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="2" role="columnheader" scope="col" tabindex="0">**<span class="notranslate">Description</span>** </th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd"><span class="notranslate">find</span> </td><td class="confluenceTd"><span class="notranslate">java.util.Collection</span> </td><td class="confluenceTd"><span class="notranslate">Retrieves a collection of business objects that are descendants of the current object. </span><span class="notranslate">Note that this method should not construct DataNode objects that will wrap the business object later.</span> </td></tr><tr role="row"><td class="confluenceTd"><span class="notranslate">newInstance</span> </td><td class="confluenceTd"><span class="notranslate">Object</span> </td><td class="confluenceTd"><span class="notranslate">Creates a new business object, filling the default value of its attributes, depending on the context in which they are creating.</span> </td></tr></tbody></table> </div><span class="notranslate">Thus, to implement the same countries data model, equivalent to the former XML file you should define the following business classes:</span> ##### <span class="notranslate">Country business class</span> ```Java package com.soffid.sample; public class Country { private String name; private String abbreviation; public String getName () {return name;} public void setName (String name) {this.name = name;} getAbbreviation public String () {return abbreviation;} public void setAbbreviation (String abbreviation) { this.abbreviation = abbreviation; } } ``` ##### City business class ```Java package com.soffid.sample; public class City { private String name; public String getName () {return name;} public void setName (String name) {this.name = name;} } ``` ##### Title business class ```Java package com.soffid.sample; public class Title { private String name; public String getName () {return name;} public void setName (String name) {this.name = name;} } ``` Now that we have the needed business classes, the next step is to create the a class that manages the root of the data tree. This class derives from SimpleDataNode because they do not allow the execution of the methods doInsert, doUpdate or doDelete. In its constructor defines two Finders, responsible for retrieving the list of countries (through a existing countryDAO class) and title (with code): ```Java package com.soffid.sample; import java.util.Vector; import es.caib.zkib.datamodel.*; import es.caib.zkib.datasource.*; public class RootNode extends SimpleDataNode { public RootNode(DataContext ctx) { super(ctx); // Title addFinder("title", new Finder () { public java.util.Collection find() throws Exception { Title t = new Title (); t.setName ( "World countries" ); Vector v = new Vector(); v.add (t); return v; }; public Object newInstance() throws Exception { throw new UnsupportedOperationException(); } }, SimpleDataNode.class); // Countries addFinder("country", new Finder () { public java.util.Collection find() throws Exception { return CountryDAO.findAll (); }; public Object newInstance() throws Exception { throw new UnsupportedOperationException(); } }, CountryNode.class); } } ``` The CountryNode object class referenced in the previous class will wrap for Country objects got by DAO. This class is responsible to retrieve and instantiate City objects from the Country. It is derived from SimpleDataNode because no update, insert or delete of countries is allowed: ```Java package com.soffid.sample; import java.util.Vector; import es.caib.zkib.datamodel.*; import es.caib.zkib.datasource.*; public class CountryNode extends DataNode { public CountryNode(DataContext ctx) { super(ctx); addFinder("city", new Finder () { public java.util.Collection find() throws Exception { Country c = (Country) getInstance(); return CityDAO.findByCountry (c.abbreviation); }; public Object newInstance() throws Exception { Country country = (Country) getInstance(); City city = new City(); city.setCountryAbbreviation (country.getAbbreviation()); return city; } }, CityNode.class); } } ``` Finally, the CityNode class is responsible for managing City persistent objects. In this case there is no finder instance because the City has no children available: ```Java package com.soffid.sample; import java.util.Vector; import es.caib.zkib.datamodel.*; import es.caib.zkib.datasource.*; public class CountryNode extends DataNode { public CountryNode(DataContext ctx) { super(ctx); } protected void doInsert() throws Exception { CityDAO.insert ((City) getInstance()); } protected void doUpdate() throws Exception { CityDAO.update ((City) getInstance()); } protected void doDelete() throws Exception { CityDAO.delete ((City) getInstance()); } } ``` <span class="notranslate">In summary, we have had to generate four kinds of objects corresponding to the three types of element of the XML document:</span> - <span class="notranslate">my-data: RootNode class derived from SimpleDataNode .</span> <span class="notranslate">It contains two finders, one for title, and one for country.</span> - <span class="notranslate">title: is using SimpleDataNode class.</span> - <span class="notranslate">country: CountryNode class derived from SimpleDataNode.</span> <span class="notranslate">It contains a finder that allows the instantiation of City objects.</span> - <span class="notranslate">city: CityNode class derived from DataNode.</span> <span class="notranslate">Implements methods to persist City object. It h</span><span class="notranslate">as no finder.</span> # Definition of dynamic models using XML descriptors Definition of dynamic models using XML descriptors # Introduction It is possible to define the underlying data model without having to write java code. To do this, you must use an XML descriptor which describes the DataNodes and their relationships. An skeleton XML descriptor has the following structure: ```XML <?xml version="1.0" encoding="UTF-8"?> <zkib-model> <datanode name="my-data"> <finder name="title" type="title"> </finder> <finder name="country" type="country"> ... </finder> </datanode> <datanode name="title"/> <datanode name="country"> <finder name="city" type="city"> ... </finder> </datanode> <datanode name="city"> ... </datanode> </zkib-model> ``` <span class="notranslate">Within the XML tag whose root is always zk-ib, you can specify one or more DataNodes.</span> <span class="notranslate">Each DataNode has a unique name.</span> Within each<span class="notranslate"> DataNode, you can define multiple finders.</span> Each<span class="notranslate"> finder specifies a name and a type. The name will be used to build xpaths, whlie the type identifies the type of DataNode this xpaths refers to.</span> <span class="notranslate">Within each finder you can define multiple search handlers.</span> They w<span class="notranslate">ill be responsible for retrieving data from persistent storage, just like the find method on the finder interface.</span> <span class="notranslate">Additionally, you can define one or new instance handlers.</span> <span class="notranslate">They will be responsible for creating new business objects on user request.</span> <span class="notranslate">Finally, each DataNode can have many persistence handlers. They will act just lik the doInsert, doUpdate and doDelete methods on DataNode class. Each type of handler can</span><span class="notranslate"> be executed conditionally, depending on expressions to be evaluated at run time.</span> <span class="notranslate">These expressions can use the following predefined variables:</span> <span class="notranslate">Additionally, EL expressions may refer to all variables defined within the DataSource. Those variables are accessed via JXPathContext.getVariables() method. </span>To use of this type of data models, simply create a datamodel component on the ZUL page and assign the src attribute the path to the XML descriptor. The path can be a web component or a class path resource. <div id="bkmrk-variable-value-self-"><div><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Variable: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div>**Variable**</div></th><th aria-disabled="false" aria-label="Value: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div>**Value**</div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">self</td><td class="confluenceTd">Current DataNode</td></tr><tr role="row"><td class="confluenceTd">instance</td><td class="confluenceTd">Business object wrapped into current DataNode</td></tr><tr role="row"><td class="confluenceTd">parent</td><td class="confluenceTd">Parent DataNode</td></tr><tr role="row"><td class="confluenceTd">parent.instance</td><td class="confluenceTd">Business object wrapped into parent DataNode</td></tr><tr role="row"><td class="confluenceTd">datasource</td><td class="confluenceTd">DataSoruce the current DataNode belongs to</td></tr></tbody></table> </div></div> # Ejb-handler Handler It is responsible for persisting the object via a stateless session bean. The following attributes are supported: <table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" id="bkmrk-attribute-usage-jndi" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div>**Attribute**</div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div>**Usage**</div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">jndi </td><td class="confluenceTd">JNDI path to EJB Home interface </td></tr><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd"> EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> <span class="notranslate">Inside the handler, you should specify the suitable insert-method, delete-method and update-method tags.</span> There is a mandatory attribute named method. This attribute must contain the name of the method to invoke. Additionally, the parameters to use can be specified. <span class="notranslate">The following example shows an ejb-handler that uses an EJB whose only parameter is the data object:</span> ```western <datanode name="network"> <ejb-handler jndi="com.soffid.sample/NetworksBean"> <insert-method method="insert"/> <delete-method method="delete"/> <update-method method="update"/> </ejb-handler> ... </datanode> ``` This second example shows how to call a method with slightly complex parameters: ```XML <datanode name="acl"> <ejb-handler jndi="com.soffid.sample/NetworksBean" > <insert-method method="grant"> <parameter value="${parent.instance}"/> <parameter value="${instance}"/> </insert-method> <delete-method methos="revoke"> <parameter value="${instance}"/> </delete-method> </ejb-handler> </datanode> ``` # Script-handler Handler It ca be used to persist the business objects using BSH scripts. Supports the following attributes: <div id="bkmrk-attribute-usage-if-e"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div>**Attribute**</div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div>**Usage**</div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd"> EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> </div><span class="notranslate">Inside the handler, you can use the insert-script, delete-script and update-script tags.</span> <span class="notranslate">Each contains the BSH script that the engine will execute to perform inserts, deletes or updates.</span> <span class="notranslate">Within the BSH script you can refer to the same EL expressions predefined variables:</span> <div id="bkmrk-variable-value-self-"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Variable: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>**Variable**</div></div></th><th aria-disabled="false" aria-label="Value: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>**Value**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">self</td><td class="confluenceTd">Current DataNode</td></tr><tr role="row"><td class="confluenceTd">instance</td><td class="confluenceTd">Business object wrapped into current DataNode</td></tr><tr role="row"><td class="confluenceTd">parent</td><td class="confluenceTd">Parent DataNode</td></tr><tr role="row"><td class="confluenceTd">parent.instance</td><td class="confluenceTd">Business object wrapped into parent DataNode</td></tr><tr role="row"><td class="confluenceTd">datasource</td><td class="confluenceTd">DataSoruce the current DataNode belongs to</td></tr></tbody></table> </div><span class="notranslate">The following example shows how to save objects in file Country:</span> ```XML <datanode name="country"> <script-handler > <insert-script> import java.io.*; f = new FileOutputStream ("country."+instance.abbreviation); oos = new ObjectOutputStream (f); oos.writeObject (instance); oos.close (); f.close (); </insert-script> <update-script> import java.io.*; f = new FileOutputStream ("country."+instance.abbreviation); oos = new ObjectOutputStream (f); oos.writeObject (instance); oos.close (); f.close (); </update-script> <insert-script> import java.io.*; f = new File ("country."+instance.abbreviation); f.delete (); </insert-script> </script-handler> ... </datanode> ``` # Collection-handler Handler <span class="notranslate">This handler is applicable when the persistence of this object is managed by the parent dataNode. The allowed attributes are:</span> <div id="bkmrk-attribute-usage-coll"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">collection </td><td class="confluenceTd">EL expression that identifies the collection onto which the business object must be added or removed </td></tr><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd">EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> </div><span class="notranslate">The following example shows how you could manage objects City as a collection of objects within the object Country, which in turn is saved on a disk arhivo:</span> ``` <datanode name="city"> <collection-handler collection="${parent.instance.cities}"> </datanode> ``` # Custom-handler Handler <span class="notranslate">The custom-handler provides coverage for situations where you need a more sophisticated handler and it is not worth to use a bsh script., In this case the persistence must be done be a java class that implements the PersistenceHandler interface, and the custom-handler specifying the name of the class used. The attributes are:</span> <div id="bkmrk-attribute-usage-clas"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">className </td><td class="confluenceTd">Name of the java class to be used </td></tr><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd"> EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> </div> # Data validation <span class="notranslate">The validation tag is responsible for performing basic checks regarding mandatory attributes and valid attribute values before being submitted to the persistence handler.</span> <span class="notranslate">The validation tag may contain one or more attribute-validation and script-validation tags.</span> <span class="notranslate">The verification will be performed before running the insert or update handler.</span> <span class="notranslate">attribute-validator contains the following attributes:</span> <div id="bkmrk-attribute-usage-expr"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" style="width: 116px;" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" style="width: 693px;" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd" style="width: 116px;">expr </td><td class="confluenceTd" style="width: 693px;">EL expression pointing to the attribute to validate </td></tr><tr role="row"><td class="confluenceTd" style="width: 116px;">friendlyName </td><td class="confluenceTd" style="width: 693px;">Text to be presented to the user on validation failure. If there is a ZK label with this text, it will be localized based on current user language preference. </td></tr><tr role="row"><td class="confluenceTd" style="width: 116px;">notNull </td><td class="confluenceTd" style="width: 693px;">true is the attribute is mandatory</td></tr><tr role="row"><td class="confluenceTd" colspan="1" style="width: 116px;">maxLength</td><td class="confluenceTd" colspan="1" style="width: 693px;">Maximum length of the attribute</td></tr><tr role="row"><td class="confluenceTd" colspan="1" style="width: 116px;">minValue</td><td class="confluenceTd" colspan="1" style="width: 693px;">Minimum value in case of numeric attributes</td></tr><tr role="row"><td class="confluenceTd" colspan="1" style="width: 116px;">maxValue</td><td class="confluenceTd" colspan="1" style="width: 693px;">Maximum value in case of numeric attributes</td></tr></tbody></table> </div><span class="notranslate">attribute-script contains a script that will be executed to validate the business object.</span> <span class="notranslate">Example:</span> ```western <datanode name="country"> <validation> <attribute-validation expr=”${instance.abbrevisation}” notNull=”true” friendlyName=”Two letter abbrv.”> <script-validation> if (instance.abbreviaton.equals(“CT”)) { throw new RuntimeException (“Catalonia is not a country yet”); } </script-validation> </validation> ... </datanode> ``` # EJB find handler: ejb-finder <span class="notranslate">Handles the method to retrieve business objects via a stateless session bean.</span> <span class="notranslate">Supports the following attributes:</span> <div id="bkmrk-attribute-usage-jndi"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">jndi </td><td class="confluenceTd">JNDI path to EJB Home interface </td></tr><tr role="row"><td class="confluenceTd" colspan="1">method</td><td class="confluenceTd" colspan="1">EJB Bean method to get business objects</td></tr><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd"> EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> </div><span class="notranslate">Additionally, it can specify one or more parameters in a way similar to the ejb-handler methods</span> <span class="notranslate">ejb-finder example</span> ```western <datanode name="country"> ... <finder name="cities" type="city" > <ejb-finder jndi="com.soffid.sample/CitiesBean" method="findByCountry"> <parameter value="${instance.abbreviation}"/> </ejb-finder> </finder> ... </datanode> ``` # script-finder handler <span class="notranslate">It is responsible for retrieve business objects from the persistence layer using BSH scripts. The following attributes are supported</span> <div id="bkmrk-attribute-usage-if-e"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid" style="height: 98px; width: 689px;"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row" style="height: 28px;"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" style="height: 28px; width: 118px;" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" style="height: 28px; width: 571px;" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row" style="height: 35px;"><td class="confluenceTd" style="height: 35px; width: 118px;">if </td><td class="confluenceTd" style="height: 35px; width: 571px;">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row" style="height: 35px;"><td class="confluenceTd" style="height: 35px; width: 118px;">unless </td><td class="confluenceTd" style="height: 35px; width: 571px;"> EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> </div><span class="notranslate">The contained bsh script must return a collection of business objects. If the returned object is not a collection object, the engine will treat the returned object as a singleton.</span> <span class="notranslate">In the following example, the script finder retrives all countries saved to disk in the previous example:</span> ```western <datanode name="root"> ... <finder name="country" type="country" > <script-finder > import java.io.*; files[] = new File(".").listFiles ( new FilenameFilter () { public boolean accept (File dir, String name) { return name.startsWith("coutnry."); } ); v = new java.util.Vector(); for (int i = 0; i < files.length; i++) { f = new FileInputStream (files[i]); ois = new ObjectInputStream (f); v.add (ois.readObject (); ois.close (); f.close(); } return v; </script-finder> </finder> ... </datanode> ``` # collection-finder handler <span class="notranslate">Similar to collection-handler, this handler retrieves the list objects contained on a parent collection.</span> <div id="bkmrk-attribute-usage-coll"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">collection </td><td class="confluenceTd">EL expression that contains the objects collection </td></tr><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd">EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> </div><span class="notranslate">The following example shows how you could retrieve City objects as a collection of objects within the County object:</span> ```western <datanode name="country"> ... <finder name="cities" type="city" > <collection-finder collection="${instance.cities}"> </finder> ... </datanode> ``` # custom-finder handler <span class="notranslate">The custom-finder provides coverage for situations where you need a more sophisticated handler and is not worth implement it using a script. In this case a class that implements the FinderHandler interface must be developed, and the custom-finder specifying the name of the class must be used</span> <div id="bkmrk-attribute-usage-clas"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">className </td><td class="confluenceTd">Name of the FinderHandler class </td></tr><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd">EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> </div> # new-instance-script handler <span class="notranslate">It is responsible for instantiating new objectswithin a finder on user request .</span> <div id="bkmrk-attribute-usage-if-e"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" style="width: 81px;" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" style="width: 458px;" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd" style="width: 81px;">if </td><td class="confluenceTd" style="width: 458px;">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd" style="width: 81px;">unless </td><td class="confluenceTd" style="width: 458px;">EL expression that must be evaluated to false prior to handler action</td></tr></tbody></table> </div><span class="notranslate">With the following example the model will alow to create a new city within a country:</span> ```western <datanode name="country"> ... <finder name="cities" type="city" > <collection-finder collection="${instance.cities}"> <new-instance-script> c = new City(); c.countryAbbreviation = instance.abbreviation; return c; </new-instance-script> </finder> ... </datanode> ``` # new-instance-bean handler <span class="notranslate">This handler allows the craetion of a new business object and assign default attribute values.</span> <span class="notranslate">The value of the bean attributes is specified using multiple instances of the bean-attribute tag</span> <div id="bkmrk-attribute-usage-clas"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">className </td><td class="confluenceTd">Name of the business object class </td></tr><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd">EL expression that must be evaluated to false prior to handler action</td></tr><tr role="row"><td class="confluenceTd" colspan="1">bean-attribute/name</td><td class="confluenceTd" colspan="1">Name of the attribute</td></tr><tr role="row"><td class="confluenceTd" colspan="1">bean-attribute/value</td><td class="confluenceTd" colspan="1">EL expression with the value to assign</td></tr></tbody></table> </div><span class="notranslate">With the following example would create a new city within a country:</span> ```western <datanode name="country"> ... <finder name="cities" type="city" > <collection-finder collection="${instance.cities}"> <new-instance-bean className="City"> <bean-attribute name="countryAbbreviation" value="${instance.abbreviation}"/> </new-instance-bean> </finder> ... </datanode> ``` # custom-attribute handler <span class="notranslate">Generates virtual attributes derived from other attributes or external elements of the application.</span> <span class="notranslate">It can be applied to any DataNode to add attributes that were not originally present at the underlying business object. Those attributes will be presented at the JXPath interface just as if they were business objects attributes</span> <div id="bkmrk-attribute-usage-name"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>Attribute</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>Usage</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">name </td><td class="confluenceTd">Name of the virtual attribute </td></tr><tr role="row"><td class="confluenceTd" colspan="1">expr</td><td class="confluenceTd" colspan="1">EL expression that evaluates de attribute value</td></tr><tr role="row"><td class="confluenceTd">if </td><td class="confluenceTd">EL expression that must be evaluated to true prior to handler action </td></tr><tr role="row"><td class="confluenceTd">unless </td><td class="confluenceTd">EL expression that must be evaluated to false prior to handler action</td></tr><tr role="row"><td class="confluenceTd" colspan="1">depends</td><td class="confluenceTd" colspan="1">XPath to a attribute or business object the expression depends on</td></tr></tbody></table> </div>It's important to properly set the depends attribute as long as the attribute will be reevaluated whenever a dependent attribute has been changed. The custom attribute can have an empty EL expressions and use a BeanShell script instead. Here is an example of both aproaches: ```western <datanode name="network"> <custom-attribute name=”networkMask1” expr=”${instance.network}/${instance.mask}”> <custom-attribute name=”networkMask2”> <depends>@network</depends> <depends>@mask</depends> return instance.network + “/” + instance.mask; </custom-attribute> ... </datanode> ``` # Using dynamic models To use dynamic models XmlDataSource tag must be replaced by datamodel. The datamodel tag has the following attributes: <div id="bkmrk-attribute-usage-id-z"><table class="confluenceTable tablesorter tablesorter-default stickyTableHeaders" role="grid"><thead class="tableFloatingHeaderOriginal"><tr class="tablesorter-headerRow" role="row"><th aria-disabled="false" aria-label="Attribute: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="0" role="columnheader" scope="col" tabindex="0"><div><div>**Attribute**</div></div></th><th aria-disabled="false" aria-label="Usage: No sort applied, activate to apply an ascending sort" aria-sort="none" class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" data-column="1" role="columnheader" scope="col" tabindex="0"><div><div>**Usage**</div></div></th></tr></thead><tbody aria-live="polite" aria-relevant="all"><tr role="row"><td class="confluenceTd">id </td><td class="confluenceTd">ZK Identifier </td></tr><tr role="row"><td class="confluenceTd">className </td><td class="confluenceTd">root DataNode class name </td></tr><tr role="row"><td class="confluenceTd">src </td><td class="confluenceTd">XML resource name for XML dynamic data model</td></tr><tr role="row"><td class="confluenceTd" colspan="1">rootNode</td><td class="confluenceTd" colspan="1">Root node type for dynamic data model</td></tr></tbody></table> </div>className usage is not compatible with src and rootNode attributes. rootNode attribute is mandatory when using XML dynamic data models.