Table of Contents

  1. Setting up and configuring Velosurf
  2. External Parameters
  3. Connections/statements pooling and recovery
  4. Reverse engineering
  5. Fetching instances and iterating
  6. IDs obfuscation
  7. Caching instances
  8. Customizing Java mapping objects
  9. Issuing modifications to the database
  10. Data validation
  11. Imported and exported keys
  12. HTTP Query parameters
  13. Localization
  14. Authentication
  15. Template name filter
  16. Order of servlet filters
  17. Using the Velosurf API from Java

Setting up and Configuring Velosurf

Please refer to the Installation page for how to set up Velosurf in you Velocity-Tools Webapp or in your Java application.

It is possible to use several instances of Velosurf at the same time, each having its own configuration file, for instance if you want to use different context keys to refer to different schemas. Provided that the different configuration files use the same database login (apart from the schema name), you can cross-reference entities between configuration files.

It is also possible to split the configuration file into several pieces using the XML Include syntax: the root <database> tag must have the following attribute set: xmlns:xi="http://www.w3.org/2001/XInclude". You can set include files using the syntax: <xi:include href="included_file.xml"/> (other features of the XML Include specification, like fallback and XPointers, are not supported).

The XML tree of the configuration file (plus optional reverse engineered data) defines everything that will be accessible under the $db (or whatever name you choosed) context key. The syntax of this configuration file is detailed on the Configuration page.

An attribute is of any of the following types::

For the row and row set types, if the rows are instances of an entity, you can specify the name of the resulting entity. Doing so, you'll be able to invoke this resulting entity attributes and actions.

The former and now deprecated syntax for declaring attributes was: <attribute name="myattribute" result="scalar|row|rowset[/resultEntity]">. Now, you can use: <scalar name="myScalar">, <row name="myRow" [result="resultEntity"]>, <rowset name="myRow" [result="resultEntity"]>

You'll be able to call this entity's attributes directly on the returned row.

Values of the current instance that are needed in the SQL query of an entity's attribute must appear as inline tags inside the SQL query:

<entity name="book"> <!-- let's suppose we want to manually define the foreign-key towards the publisher table --> <row name="publisher" result="publisher"> SELECT * FROM publisher WHERE id = <publisher_id/> </row> </entity>

Please note that the $book.publisher property will automagically be available, without any need to declare it, when using the full database reverse enginering mode (see later).

Be careful when using quoted SQL strings in your queries: since XML parsers may add spaces here and there inside text data, it may break the syntax of such SQL queries. To remedy to this problem, you must use the XML attribute xml-space="preserve" as an XML attribute of any XML entity containing such an SQL query.

Remember also to escape XML special characters < > and & with their XML equivalent (respectively &lt; &gt; and &amp;) whenever they appear inside your queries.

External Parameters

Other parameters - called external parameters - can appear as inline tags in an attribute SQL query. For instance, let's say we want to define a root attribute listing all the books published in the given year. We'll define this attribute like this:

<rowset name="books_per_year" result="book"> SELECT * FROM book WHERE DATE(publication_date) = <publication_year/> </rowset>

There are two ways to use external attributes from templates:

The first one is to set their values as properties of the parent object (this method is now strongly discouraged, since the parent object can be shared), like this:

#set($db.publication_year = '2006') Books for year $db.publication_year: #foreach($book in $db.books_per_year)   * $book.title #end

The second -and now preferred- method is to specify the map of all external parameters values as argument of the attribute:

Books for year 2006: #foreach($book in $db.books_per_year({'publication_year':2006}))   * $book.title #end
To be able to use this second method, you need to use the VelosurfUberspector:
(excerpt from velocity.properties) # Use the Veltools' WebappUberspector to allow standard getters and setters on request, session and application # Use the VelosurfUberspector to allow parametrized getters runtime.introspector.uberspect = org.apache.velocity.util.introspection.UberspectImpl,org.apache.velocity.tools.view.WebappUberspector,velosurf.util.VelosurfUberspector

Without using this customized uberspector, you can still use this method by doing:

Books for year 2006: #foreach($book in $db.getWithParams('books_per_year',{'publication_year':2006}))   * $book.title #end

Connections/Statements Pooling and Recovery

Connections and statements pooling is transparent. There are two connections pools, one with autocommit connections for queries and single statement updates and one without autocommit for transactions (actions containing several update requests). Those pools grow according to the rate of requests.

The automatic connection recovery will transparently care for broken or timed out connections.

Needed statements are created on the fly when needed, that is when there isn't any previous instance of a particular statement or when such an instance is already busy. The statements pool will thus grow as needed.

Concurrent accesses are of course taken into account: Velosurf maintains a busy state for each connection.

Reverse Engineering

There are four reverse engineering modes proposed by Velosurf:

  • full (default): all tables and foreign-keys of the current schema (if any) are reverse engineered.
  • tables: all tables of the current schema (if any) are reverse engineered.
  • manual: only entities listed in the configuration file are reverse engineered (only if they do correspond to an actual table).
  • none: no reverse engineering of any kind is achieved. Several features of Velosurf (like the entity.fetch method) aren't available when using this mode.

See the Imported and Exported Keys section below for details on the reverse engineering of foreign keys.

By default entity names are deduced from SQL table names and entity property names are deduced from SQL column names.

Use the table='table name' if an entity is to have a different name than its corresponding table and use a <aliases alias1='column1' alias2='column2' > tag to declare aliases for SQL column names.

When a table name or an SQL column name is aliased, you have to use the alias rather than the column name in whatever attribute or tag in the configuration file, except when the name appears as a keyword of an attribute SQL query.

Example:

<entity name="person" table="tbl_person"> <aliases id="person_id" lastname="family_name"/> <rowset name="related" result="person"> SELECT * FROM tbl_person WHERE family_name = <lastname/> </rowset> <scalar name="children"> SELECT count(*) FROM person WHERE person_id = <id/> </scalar> </entity>

Fetching instances and iterating

Unless reverse engineering is deactivated, primary keys of tables will be reverse engineered. You can then use the fetch(key) entity method to fetch a particular instance of this entity. The key parameter can be a scalar value, a list of values or a map of key-value pairs. The two last forms are adequate for multi-valued primary keys.

For instance:

#set( $book = $db.book.fetch( $query.book_id ) )

This example illustrate a common practice, which is to add hidden id fields to HTML forms so that target instances can be fetched using the HTTP query tool.

Multivalued keyed rows can also be fetched the same way by providing a column→value map (like $query itself) or a list containing key values in their natural order.

You can iterate on an entity or on a row set attribute by mean of the Velocity #foreach directive.

Example:

List of books by publisher:
#foreach($publisher in $db.publisher)
  Books published by $publisher.name:
    #foreach($book in $publisher.books)
      - $book.title (author $book.author.firstname $book.author.lastname)
    #end
#end

While discouraged since it breaks SQL code isolation, you can control the rows order and add supplemental criteria by calling the order("SQL order clause") and refine("SQL condition") methods before issuing the #foreach, like this:

List of books by publisher:
#set($db.publisher.refine('active=true')) ## only take into account active publishers
#set($db.publisher.order('name')) ## order by name
#foreach($publisher in $db.publisher)
  Books published by $publisher.name:
    #foreach($book in $publisher.books)
      - $book.title (author $book.author.firstname $book.author.lastname)
    #end
#end

Refinment and ordering have the same scope than the velosurf.web.VelosurTool tool.

IDs obfuscation

Since in a Webapp the query part of URLs is likely to contain ID values, it is good practice to obfuscate those values if you want to protect the Webapp against manual editing of those URLs. Velosurf can automatically handle this obfuscation for you. You just need to provide a comma separated list of columns meant to be obfuscated in the obfuscate attribute of the <entity> tag.

Remember that obfuscated IDs will be strings, never numbers.

Caching instances

When very frequent fetch queries occur, you can tell Velosurf to cache corresponding instances in memory by mean of the cache attribute of the <entity> tag, which can take the following values:

  • no (the default): no caching provided.
  • yes: caching of fetched instances with respect to memory. Instances are put into the cache when fetched for the first time, but the Java virtual machine can reclaim the memory they use if needed.

Warning: Velosurf will invalidate cached entries on update/delete requests, but global updates are not taken into account, so be sure to empty the cache after global modifications.

This caching mechanism is meant for straightforward optimizations in simple situations, for instance to avoid re-fetching a specific instance at each HTTP request.

Customizing Java mapping objects

Using the class attribute of the entity tag, you can specify which class you'd like Velosurf to use to map instances of a particular entity. This class can be a POJO (Plain Old Java Object) or a class inheriting from velosurf.context.Instance.

In both cases, the following methods will be taken into account by Velosurf when present in the POJO:

When using POJOs, you can choose to implement only some of fields getters and setters; other fields will still have their default getters and setters.

Issuing modifications to the database

By default, the database is opened in read-only mode, and thus forbidding the execution of any action, insert, update or delete. You need to set a read-only="no" attribute in the database tag to override this default behaviour.

To enforce the MVC paradigm, database modifications should not be issued from inside templates but rather from a controller object, so as not to mix View and Controller layers. This controller object of your own can change programmatically the read-only flag of its connection before issuing the modifications, while still relying on the model defined in Velosurf configuration file via the Velosurf Java api. And to enforce security, you should also use different database users with different rights (see the FAQ about how to do this).

For each row-based update or delete, Velosurf ensures that the key values are known to avoid a wider accidental update or delete.

After an insertion, the last inserted ID value can be fetched using $db.entity.lastInsertID (or from Java code by calling the getLastInsertID() method on the EntityReference). Note: this feature is not implemented in Velosurf for all databases (for now, only Cloudscape, DB2, HSqlDB, MySql and Sybase databases are supported - if you need this feature for your favorite RDBMS not listed here, please contribute!)

Data validation

Warning: the data validation module is currently not actively maintained - it may become depracated, evolve into something else, etc...

Velosurf provides a validation process on constraints defined in its configuration file. Those constraints don't replace SQL defined constraints, they are supplementary constraints provided by Velosurf which are enforced on a per-row basis and not checked on massive updates.

You can define constraints on fields of an entity using a <constraint> tag per column. Each column constraint tag contains field constraints. Field constraints can be expressed in a short syntax form (as attributes of the column <constraint> tag) or in a long syntax form (as child tags of the column <constraint> tag), the long syntax form allowing some additional settings on the field constraint like the customization of the error message to be generated when the field constraint fails.

The following field constraints are available (we only recall the short syntax here; please refer to the Configuration page or to the javadoc):

Apart from not-empty and not-null, all constraints are considered valid on a null or empty string.

Validation occurs:

In all cases, all validation error messages are then accessible in the $db.validationErrors list.

The validation filter checks every request for a velosurf.entity query attribute that contains the name of the entity against which the data is to be validated. If found, it will check form data and either let the request pass through if data is valid or redirect back the client browser to the input form (using the referrer field) with $db.validationErrors populated if data is not valid.

Once the filter in set up in you /WEB-INF/web.xml file with those lines:

<filter> <filter-name>validation</filter-name> <filter-class>velosurf.validation.ValidationFilter</filter-class> </filter> <filter-mapping> <filter-name>validation</filter-name> <!-- on a production site you can optimize this mapping with an <url-pattern> entry per input form page if you have a 2.4+ servlet container --> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping>

then every input form can benefit of this mechanism provided:

Imported and Exported Keys

When reverse engineering foreign keys in full mode, each foreign key will produce two new attributes:

  • one attribute for the imported key, belonging to the importing table, named after the imported table name, with a row result type.
  • one attribute for the exported key, belonging to the imported table, named after a rough pluralization ("s" or "es" added) of the importing table name, with a row set result type.

Example: if the book table is importing the key of the publisher table, then the two generated attributes will be:

  • $book.publisher (a single publisher)
  • $publisher.books (a set of books)

If this default behaviour is not the one you need, use a lower reverse engineering mode and define manually the foreign keys you need using the <imported-key> and <exported-key> tags. You can still use those tags in full reverse engineering mode to customize the name of the generated attributes.

HTTP Query parameters

Deprecated: those features are now directly accessible in VelocityTools 2.0+

Velosurf provides an HTTP query parameter parser, traditionally mapped to the $query key in the toolbox. It is very similar to the VelocityTools ParameterTool (from which it inherits), but adds a few features:

  • it adds a generic setter, this allows adding some key/value pairs in its map before using $query as an argument to one of the Velosurf methods expecting a map.
  • it permits to gather in a submap all form parameters that share a common prefix. For instance, given the following form:
    <form> <input type=... name="foo.firstname" /> <input type=... name="foo.lastname" /> ... </form>
    then $query.foo will return a map containing two keys, firstname and lastname

Localization

this feature is still experimental

Velosurf provides a localization mechanism. It consists of:

Please refer to the corresponding javadoc and look in the examples for how to configure those tools.

Once the localizer is set up in the toolbox, the syntax used to display a localized message in a template will be like: $local.welcomeMessage. When localizing parameterized messages, the getter that takes parameters must be used: $local.get('welcomeMessage',$user.name)

Here is an example of configuration where we want the client browser redirected towards pages under /en/, /fr/ or /es/ if needed:

Using the redirect method is more advised than using the forward method, since the forward method will let one URL correspond to different web pages, thus bugging search engines.

Authentication

Velosurf is shipped with some utility classes that allow one to easily plug a session based CRAM (Challenge Response Authentication Mechanism) in a Webapp. It consists of three classes and a javascript module:

Please refer to the provided javadoc links for further information regarding the configuration parameters.

Here is an example of configuration for a HmacMD5 autentication:

  • file /login.vhtml:
  • <html> <head> <title>Login</title> <script type="text/javascript" src="md5.js"></script> <script type="text/javascript"> function sendLogin() { document.hidden.elements['login'].value = document.login.elements['login'].value; document.hidden.elements['password'].value = b64_hmac_md5(document.login.elements['password'].value,'$auth.challenge'); document.hidden.submit(); } </script> </head> <body> $!loginMessage<br> Identification:<br> <form name="login" action="javascript:sendLogin()" method="POST"> Login: <input type="text" name="login" size="25" maxlength="30"><br> Password: <input type="password" name="password" size="15" maxlenght="50"><br> <input type=submit value="Ok"><br> </form> <form name="hidden" action="login.do" method=POST> <input type=hidden name=login> <input type=hidden name=password> </form> </body> </html>
  • loging out link: <a href="logout.do">Logout</a>
  • Once a user has logged on, $auth.loggedUser will contain the user's instance.

    Template name filter

    Deprecated: the good practice is to use .vhtml, .vjs, .vxml ... for template file extensions

    Velosurf comes with a TemplateNameFilter servlet filter that is used to mask the '.vtl' in URLs. The idea is to be able to change the status of an HTML file from plain HTML to templatized HTML without the need to update URLs. It supposes you follow the convention of suffixing '.vtl' to the name of template files (e.g. index.html.vtl or toolkit.js.vtl).

    Check the javadoc to see how you can customize the parameters of the filter.

    Order of servlet filters

    When using several of the filters proposed by the library, one must be cautious to the order in which those filters are mapped to incoming HTTP requests in the /WEB-INF/web.xml application descriptor.

    If used, the template name filter should appear first. Then, if both the authentication and localization filters are used, they should appear in the same order as the hierarchical ordering of the corresponding directories (i.e. authentication first if you use paths like /auth/en/ and localization first if you use paths like /en/auth/).

    Using the Velosurf API from Java

    The Velosurf API is a very convenient way to access to the database from Java while still centralizing your model in model.xml.

    The main classes you may have to use are the following ones:

    But you can also stick to velosurf.context objects, that is use EntityReference instead of velosurf.model.Entity and velosurf.context.AttributeReference instead of velosurf.model.Attribute. It all depends on your needs.

    Please note that to avoid sql connection timeouts, you should not declare Velosurf prepared statements as variables having a long lifecycle (like static members). You should only keep references to Velosurf connection objects on the long term (they do handle timeouts). Plus, they already do prepared statements caching and pooling for you.