Server extensions are also useful in the area of derived fields. A derived field is an information element that is not stored within a document, but is computed when the document is retrieved. This is usually done to avoid redundant data within a document, and in cases when the value of a document node depends on the environment.
In XQuery, derived values can be generated by using
constructors in the return
clause. The remainder of this section
describes how to deal with derived elements using X-Query.
A typical example is a person's age. We can store a person's date of
birth in a document, but we cannot store his or her age because it changes over
time. However, with server extensions we can define an age
element
in a document that describes a person, for example in
jazzMusician
. When such a document is retrieved, we must compute
the value of this element by subtracting the date of birth from the current
date.
Because we rely on values from other document nodes – here
birthDate
– during the computation of a derived field, we must
make use of a callback function. There are three types of callback functions:
XML, ODBC and System. It is the XML callback functions that we need, because we
want to query Tamino for the birthDate
element.
Here is the complete code for the server extension. The callback code is highlighted.
// encyclopedia_jazzMusician.java: // Implementation of server extension encyclopedia_jazzMusician: package tamino.SXS.jazz.encyclopedia.jazzMusician; import com.softwareag.ino.sxs.*; import java.util.Date; import java.text.SimpleDateFormat; // Javadoc comments. /** * All extensions for jazz database * @author Berthold Daum * @version 1.0 */ public class encyclopedia_jazzMusician extends ASXJBase { /** * The default constructor * (No other constructor allowed.) */ public encyclopedia_jazzMusician () { } /** * compute age from birth date (map-out function) * @param object_id parameter * @param element_id parameter * @param document parameter */ public void computeAge (int object_id, int element_id, StringBuffer document) throws java.lang.Exception { // create query string for callback String xmlQuery = "jazzMusician[@ino:id='"+object_id+"']/birthDate"; // create new string buffer for call back results StringBuffer response = new StringBuffer(1024); // callback: XQL query int ret = SxsXMLXql ("encyclopedia", xmlQuery, response); // Here we dive deep into the rather complex error // handling for server extensions callbacks. if (ret != 0) { // identify the callback error int msgNo = SxsGetMsgNo (); switch (msgNo) { case INO_ERROR: StringBuffer msgBuf = new StringBuffer ( ); // Get the Tamino Server message number int inoMsgNo = SxsGetInoMsgNo ( ); // Get the Tamino Server messageline ret = SxsXMLGetMessage (msgBuf); throw (new Exception("INO_ERROR: " +inoMsgNo+" "+msgBuf)); default: // Get the SXS message corresponding to callback error String msg = SxsGetMsgText (); throw (new Exception("SXS Error: "+msgNo+" "+msg)); } } // error handling done, now the real logic // Remember, we had obtained the birthDate in "response" // Convert into string String docstr = response.toString(); // We have to isolate the value of the birthDate // element from the Tamino response string. // Could be properly done with SAX, // but to be brief we do it by hand. int t = docstr.indexOf("<birthDate", 0); // we have to scan for tag begin and end separately // because the tag includes attributes (ino:id). int b = docstr.indexOf('>',t+10)+1; // Find end of element int e = docstr.indexOf("</birthDate>", b); // Convert content into Date format SimpleDateFormat df = new SimpleDateFormat ( "yyyy-MM-dd" ); Date d1 = df.parse(docstr.substring(b,e-1)); // Now extract years and months // These methods are deprecated, but we use them anyway. int y1 = d1.getYear(); int m1 = d1.getMonth(); Date d2 = new Date(); int y2 = d2.getYear(); int m2 = d2.getMonth(); // Compute age int age = y2-y1; // Adjust age, if we are still // before this year's birthday if ((m1 > m2) || ((m1 == m2) && (d1.getDate() > d2.getDate()) )) age--; // Now pack into appropriate tags docstr = "<age>"+age+"</age>"; // Write element to result buffer document.replace (0, docstr.length(), docstr); // To delete computeAge, remove it here and from Install.xml. } }
After the usual process – compilation, packing, and installation – we can
use this server extension. First, we must define an element
<age>
in the corresponding jazzMusician
schema:
The resulting schema fragment looks like this:
<xs:element name = "age" type = "xs:short"> <xs:annotation> <xs:appinfo> <tsd:elementInfo> <tsd:physXNode> <tsd:mapXTension> <tsd:onCompose> encyclopedia_jazzMusician.computeAge </tsd:onCompose> </tsd:mapXTension> </tsd:physXNode> </tsd:elementInfo> </xs:appinfo> </xs:annotation> </xs:element>
After making this schema known to the database, we can store
jazzMusician
document instances. These instances must include
dummy <age>
elements:
<age>0</age>
If we did not supply such elements (if we had defined
minOccurs="0"
in the element declaration), the
computeAge
server extension would not be called when the document
is retrieved, and no age would be computed and displayed.
Tip:
If a server extension derives a value for an attribute
rather than for an element, and a default value for the attribute is specified
in the schema, it is not necessary to provide a dummy value for the attribute.
This is because Tamino stores document instances with default attribute values
if explicit attribute values are not provided.
Tip:
You can allow a default value to be generated for an
element or an attribute by using a server extension Query function. Refer to
the section tsd:default in the
document Tamino XML Schema Reference Guide for
details.
When we now query jazzMusician
documents, the current age of
each jazz musician is displayed. For example, the following query issued in
October 2004:
jazzMusician[@ID="ColtraneJohn"]
results in:
<jazzMusician ino:id="1" type="instrumentalist" ID="ColtraneJohn"> <name> <first>John</first> <last>Coltrane</last> </name> <birthDate>1926-09-23</birthDate> <age>78</age> </jazzMusician>
The same query issued in October 2002 results in an age of 76.
The derived field can also be used in queries:
jazzMusician[age > 70]
However, because we cannot index derived fields, search criteria involving derived fields are always processed in the post-processing phase of a query (see From Schema to Tamino::Efficient Queries).