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"]>
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:
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 <
>
and &
) whenever they appear inside your queries.
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:
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:
The second -and now preferred- method is to specify the map of all external parameters values as argument of the attribute:
Without using this customized uberspector, you can still use this method by doing:
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.
There are four reverse engineering modes proposed by Velosurf:
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:
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:
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.
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.
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.
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:
getFoo()
.get(key)
setFoo(value)
put(key,value)
update()
(boolean result type expected)update(map)
(boolean result type expected)insert()
(boolean result type expected)delete()
(boolean result type expected)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.
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!)
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):
not-null="yes"
: data cannot be null.not-empty="yes"
: data cannot be null or an empty string.min-length="integer"
and/or max-length="integer"
: data length must reside in the specified inclusive interval.one-of="value1,value2,..."
: data must be one of the supplied values.references="table.column"
: data must reference a value found in table.column
(a select distinct
query is performed at validation time).regex="pattern"
: data must be matched by the supplied pattern.type="number"
: a numeric value is expectedtype="integer"
: an integer or long value is expected.min="number"
and/or max="number"
: data must be in the specified inclusive interval (number type implied).type="date"
: data must be a parsable date. Some heuristics are provided to determine the locale and the format of the date; otherwise you can use the long syntax form and
specify the format you expect here.after="yyyymmdd"
and/or before="yyyymmdd"
: data must be a date lying between the specified inclusive interval (date type implied).type="email"
: data is expected to have a valid email syntax. When using the long syntax form for this constraint, you can also ask for a DNS check
(to validate the domain name) and for an SMTP check (to validate the user name).Apart from not-empty
and not-null
, all constraints are considered valid on a null or empty string.
Validation occurs:
update()
or insert()
is called on a row (boolean returned).validate()
is called on a row (boolean returned).velosurf.validation.ValidationFilter
servlet filter, see below.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:
then every input form can benefit of this mechanism provided:
<input type="hidden" name="velosurf.entity" value="entity name">
.When reverse engineering foreign keys in full mode, each foreign key will produce two new attributes:
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.
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:
$query
as an argument to one of the Velosurf methods expecting a map.$query.foo
will return a map containing two keys, firstname and lastnameVelosurf provides a localization mechanism. It consists of:
velosurf.web.l10n.Localizer
Java interface with a getter and a setter for the active locale, and two getters to obtain
localized messages from their ID (the second one allowing the use of parameterized messages as in "Found {0} match(es) for {1}."
).velosurf.web.l10n.HTTPLocalizerTool
Java class implementing the Localizer
interface.
It will try to deduce the appropriate locale for incoming HTTP queries (from their "Accepted-Language" header). It is abstract because it does not make any assumption
on how the localized data is stored.velosurf.web.l10n.SimpleDBLocalizer
Java class inheriting from HttpLocalizerTool
that uses a configurable localized(id,locale,string)
SQL table.velosurf.web.l10n.LocalizationFilter
servlet filter meant to help the process of redirecting or
forwarding incoming requests towards separate localized instances of a site (i.e. /index.html
redirected
or forwarded towards /en/index.html
or /fr/index.html
) based on browser locale. This rewriting mechanism is
only implemented for the URI part of the URL at the moment (that is, you cannot yet change the hostname (→en.mysite.com
) or the query string (→mysite.com?lang=fr)).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:
/WEB-INF/web.xml
:/WEB-INF/toolbox.xml
(VelocityTools 1.x):/WEB-INF/tools.xml
(VelocityTools 2.x):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.
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:
velosurf.web.auth.AuthenticationFilter
that is meant to be mapped to pages needing authentication.velosurf.web.auth.BaseAuthenticator
tool that takes a method
configuration parameter
(the encryption method to be used). Omitting this method
parameter means that passwords will be transmitted in clear (which is
not a problem if you use HTTPS, which you should really use if you want security).velosurf.web.auth.SimpleDBAuthenticator
, that implements the two abstract
methods getUser(login)
and getPassword(login)
using a configurable user(id,login,password)
SQL table. If you use this authenticator, you'll have to define a root attribute that returns a user instance given its login, as detailed on the javadoc page./src/javascript/md5.js
(BSD license) implementing the client-side encryption for the HmacMD5
method.Please refer to the provided javadoc links for further information regarding the configuration parameters.
Here is an example of configuration for a HmacMD5 autentication:
/WEB-INF/web.xml
:/WEB-INF/toolbox.xml
(VelocityTools 1.x):/WEB-INF/tools.xml
(VelocityTools 2.x):/login.vhtml
:<a href="logout.do">Logout</a>
Once a user has logged on, $auth.loggedUser will contain the user's instance.
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.
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/
).
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:
velosurf.Velosurf
to obtain a database connection with velosurf.getDatabase()
velosurf.model.Entity
obtained from database.getEntity('name')
.velosurf.model.Attribute
obtained from database.getAttribute('name')
or from entity.getAttribute('name')
.
Attributes can then be evaluated with the appropriate method depending on the result type of the attribute (evaluate()
, fetch()
or query()
).
velosurf.model.Action
obtained from database.getAction('name')
or from entity.getAction('name')
.velosurf.context.RowIterator
obtained from entity.iterator()
or from attribute.query()
.velosurf.context.Instance
fetched by its key value or as the result of an attribute.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.