<%@page import="java.io.InputStream"%> <%@page import="java.io.InputStreamReader"%> <%@page import="java.io.BufferedReader"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/fenix-renderers.tld" prefix="fr" %>

Appendix A: Schemas

About schemas

Schemas represent a certain view over an certain type of objects. The schema contains information about which and how each slot should be presented or edited (presented will be used for both situations).

Starting with the basic

<schema name="person.simple-admin-info" type="net.sourceforge.fenixedu.domain.Person">
  <slot name="nome"/>
  <slot name="username"/>
  <slot name="email"/>
</schema>

Each schema must by assigned a unique name. Schemas are referred by name from several locations so they must be easily identifiable. Additionaly you also need to specify the name of the target type. Several functionalities in the schema depend on the type of the schema.

The example shows the simples way of using a schema. A set of slots is declared meaning that whenever the schema is presented only those slots should be visible. No matter were each slot will be presented, the presentation will obey the slot's declared order. This means that, according to the example, the slot nome will appear before the slot username and this one will appear before email. When editing a single object this specifies, for example, the order of the rows and when editing several objects this chooses the order of the columns.

How to costumize the presentation of slots?

When you want to present an object you choose the schema, layout, properties of that layout. The schema reffers the several slots that should be presented so it's possible to also specify the same elements for each slot. To do that you msut use the slot's attributes layout and schema, and specify several properties for the slot.

<schema name="person.simple-admin-info.extended" type="net.sourceforge.fenixedu.domain.Person">
  <slot name="nome"/>
  <slot name="username" layout="link"/>
  <slot name="email">
      <property name="link" value="true"/>
  </slot>
  <slot name="pais" schema="country.short" layout="values">
      <property name="htmlSeparator" value=" - "/>
  </slot>
</schema>

In this example the email and pais slots have their presentation customized. In the email slot we are just passing properties to the renderer associated with the default layout for slot's type. Nevertheless, in the pais slot, we are specifying both the schema and attributes and passing a property to the renderer associated with the layout values.

I18n in slot's. What does that mean?

Each slot has a label that can be shown next to the value or value's editor. This label is the slot's name or a resource message if the key is defined in the module's default bundle. The conventions used to determine the slot's label are explained in the section about labels in the output examples.

You can also override the used convetion and directly specify a key and bundle for each slot. This will be used to obtain the label for the slot. The schema also has an attribute bundle. This attributes avoids repeating the same bundle in all the slot's. So

<schema ... bundle="BUNDLE"> ...

is the same as

<schema ...>
  <slot ... bundle="BUNDLE"/>
  <slot ... bundle="BUNDLE"/>
  ...
</schema>

NOTE: You can specify the bundle for the whole schema and then override the bundle for a particular slot.

Read only slots

Slot's can be marked read only. This is only significant when the schema is being used in an input context. If the object is only being presented then this information is obviously ignored. By default all slot's are writable so you normally you only specify slot's that should not be writeable.

<slot ... read-only="true"/>

Validate input

The input validation is done by validators. Validators are Java classes that extend pt.ist.fenixWebFramework.renderers.validators.HtmlValidator. To validate a slot you need to specify the class of the validator that will be used.

<slot ... validator="net.sourceforge.fenixedu.presentationTier..."/>

Since validators are supposed to be generic and reusable thy can be configured with the standard properties system. To pass properties to a validator you need to specify the validator as a inner tag.

<slot ...>
    <validator class="pt.ist.fenixWebFramework.renderers.validators.RegexpValidator"/>
        <property name="regexp" value="\p{Digit}+"/>
    </validator>
    ...
</slot>

Convert user input

Sometimes you may need to provide a custom converter for the user input. For example if you are accepting an Date but want to convert the user input to an sql.Date or any other subtype. Converters are also Java classes so you also need to specify the specific converter to use.

<slot ... converter="net.sourceforge.fenixedu.presentationTier..."/>

Converters should only depend on the type and user input so it's not possible to pass properties to those converters. For example, associating an sql.Date converter to a Date field should understand the user input for that field, possibly reuse the conversion already available for the Date type and then create a new sql.Date.

Schemas can extend other schemas

Suppose you define a schema to show the personal details of a person. It could contain about 20 well configured and highly tailored slot definitions. Now if you want, in other context, to show the personal information and the information about the person's filiation you are in trouble. You need a new schema but how do you reuse the last schema?

Well, if you were thinking Copy&Paste that always works but is not what I want to say. If you simply want to present the information you can create a new schema with the slots for the filiation's information an present the person two times: the first with the first schema and the second with the schema you just created. Off course you will have all the drawbacs of having two presentations: You can't really mantain an uniform presentation of all the information together and you can't use the same strategy to edit that information.

You can reuse previous schemas by extending them. The extension mechanism is in some (and few) ways similar to the extension in OO languages. Let's use an example to show how things work:

<schema name="person.personal" type="net.sourceforge.fenixedu.domain.Person">
  <slot name="name"/>
  <slot name="birthdate"/>
  <slot name="email"/>
</schema>

<schema name="person.filiation" type="net.sourceforge.fenixedu.domain.Person" extends="person.personal">
  <remove name="email"/>
  <slot name="nameOfFather"/>
  <slot name="nameOfMother"/>
</schema>

<schema name="person.filiation.edit" type="net.sourceforge.fenixedu.domain.Person" extends="person.filiation">
  <slot name="name"      read-only="true"/>
  <slot name="birthdate" read-only="true"/>
  <slot name="email"     read-only="true"/>
</schema>

In this example the person.filiation schema is extending the person.personal schema. Additionally we are removing the email slot in the person.filiation schema. This means that the schema will have 4 slots in the order name, birthdate, nameOfFather, and nameOfMother.

The remove tag indicates slots that you don't want to include/inherit. This is preformed before any other thing is considered in the schema that is extending, in this case person.filiation. You can also "override" slot's definitions. If you declare a slot with a name that exists in the parent then you are overriding it. This means that the slot will remain in the position it was first declared but will only have the properties of it's last declaration. In the last schema, named person.filiation.edit, we are extending the person.filiation schema and indicating that the slot's included/inhertited from the person.personal schema are read only. Nevertheless this schema contains all the slot's in the same order as person.filiation. All the new slot's are included after existing slot's.

A note about problems of reuse. Schemas are intended to be reused in several presentation through out the application. So, if you change a schema check the schemas that are extending that one. If your changes are too big you can choose to make a new schema or change the name and check wich dependencies fail (schemas can only extend existing schemas and an errors will be reported).

Refine vs extend

You can also refine a previously define schema by using declaring something like:

<schema name="person.filiation.refined" type="net.sourceforge.fenixedu.domain.Person" refines="person.personal">
  <remove name="email"/>
  <slot name="nameOfFather"/>
  <slot name="nameOfMother"/>
</schema>

This example is similar to the previously. The difference is that the schema person.personal is also changed. In the end the schema being defined and person.personal will be equal, that is, will contain the same slots and the same definitions.

This technique can be usefull to separate changes made to a schema from the initial version of the schema will keeping the most up-to-date definition. Nevertheless this technique should be used with care since it's no longer be obvious wich slot's are included in a schema when it's used.

Default values

The slot's attribute default can be used to specify the default value for the slot when no value is available, as in this case. The specified value will be converted to the type of the slot and then presented. From example, for a enumeration you can specify as the default value the name of the enum. Here is an example with some default values:

(...)
    <slot name="username" default="pxxxxx" (...)
    <slot name="idDocumentType" default="IDENTITY_CARD" (...)
    <slot name="maritalStatus" default="UNKNOWN" (...)
(...)

Default values are simple and are directly tied to the schema, that is, to the place were you tell how you want the object to be presented. The default value, as presented before, allows you to statically specify values that are independent from the context were the schema is being used. This works reasonably well for enums or some strings but is less good for complex values that cannot be easily represented as a string. Check the the dynamic values section of the input example for more information.

Special setters and custom constructor: a better domain integration

Normally the renderers architecture use the standard Java Beans notation. Objects that need to be created are created using the default constructor and each slot's value is obtained with the standard getter and altered with the standard setter.

Nevertheless when we need to ensure some consistency in the changed or created objects we need to invoke more complex constructors or invoke setters that take multiple arguments and do verifications that can only be done correctly if all the values are available at the same time.

Schemas were extended to allow you to specify that information when needed. For each schema you can indicate wich constructor is called instead of the default construtor and wich special setters replace the standard setters. Here is an generic example:

<schema ... constructor="s1,s3:java.sql.Date,s2:Integer">
  <slot name="s1"/>
  <slot name="s2"/>
  <slot name="s3"/>
  <slot name="s4"/>
  <slot name="s5"/>
  <slot name="s6"/>
    
  <setter signature="setSomething(s4,s5)"/>
</schema>

This example is generic to exercise all the possibilities of this mechanism. The constructor for the object is specified as the constructor attribute of the schema. You can only specify one constructor for that schema. Special setters are declared using the setter inner tag. You can declare multiple setters. For both the constructor and setter you specify a signature. The main difference between them is that you don't (can't) specify the name in the constructor. Nevertheless the way that signature identifies the correct method/constructor is the same.

Each signature consists of a name (as said only valid in the setter signature) and a list of parameters separated by commans. Each parameter consists of slot name and can be accompanied by a type declaration. If the type is not given then it's assumed to be the type of the slot. The type can be given to force the parameter type to a subclass of the slots type. For instance if slot s3 is of type java.lang.Date then, in the example, we are forcing the parameter to the subtype java.sql.Date.

Explaning the example, we can see that whever we use this schema to create an object then the object will be created using a constructor that has 3 parameters with the types

  1. type of slot s2
  2. java.sql.Date
  3. java.lang.Integer

and passing as arguments the values of the slot s1, s3, and s2 respectively. After creating the object the slots used in the constructor are considered seted so we are left with the slots s4 to s6 to set. The slots s4 and s5 are referred in a special setter. so this setter is used. This means that after creating the object the setter setSomething is called passing as arguments the values of the slots s4 and s5. This slots are considered seted so the only slot left is s6. This slot was not used in the constructor neither was he used in a special setter so it will be seted using the standard setter setS6(s6).

Declare hidden slots

In the constructor and setters signature you can only reffer slots defined in the schema. The problem is that in most cases values that you need to specify in the constructor, for example, are context dependant, that is, they are passed as hidden slots from the JSP.

To overcome this problem you can declare those slots also in the schema and mark them as hidden with the hidden atrribute. These slots will not be considered in the presentation but will be available to the constructor and setter declaration. Off course that if no hidden slots is really passed in the JSP the constructor will be passed the null value for that slot. See more in the hidden slots example.

<slot ... hidden="true"/>

The DTD

<%
    
    String dtdLocation = "/WEB-INF/schemas/fenix-renderers-schemas.dtd";
    InputStream stream = pageContext.getServletContext().getResourceAsStream(dtdLocation);
    
    if (stream == null) {
        out.write("Could not find DTD in " + dtdLocation);
    }
    else {
        InputStreamReader reader = new InputStreamReader(stream);
        BufferedReader buffered = new BufferedReader(reader);
        
        String line = null;
        while ((line = buffered.readLine()) != null) {
            out.write(
                    line.replaceAll("&", "&")
                        .replaceAll("<", "<") 
                        .replaceAll(">", ">")
                        .replaceAll("\"", """)
                        + "\n");
        }
    }

%>

Top