Agnos processes an Interface Description Language (IDL) specification of a service, and generates target-language bindings. This IDL is written as a very simple XML document, the semantics of which are discussed here.
Before reading this, be sure to read the Concepts and Data Types.
The root element of every IDL file is the <service> tag, which may contain consts, enums, typedefs, records, exceptions, classes, and functions – each of these element may in turn contain other sub-elements too.
Every IDL element may include documentation, either as an attribute (doc="foo bar") of the element itself, or as a sub-element called <doc>. For instance:
<func name="foo" type="void">
<doc>this is a documentation element, providing description
for the function foo. note that this text may span across several
lines. you may use ReST formatting within comments, to add formatting
to your documentation.</doc>
<arg name="bar" type="int" doc="here's a short description of the argument 'bar'" />
<arg name="spam" type="float">
<doc>and this is equivalent to the ``doc`` element</doc>
</arg>
</func>
Every IDL element may specify its unique ID (an integer), as the attribute id. These ID numbers are used to identify functions, methods, and attributes (properties) in the protocol level, and are thus important to multi-versioning (meaning, multiple versions of the IDL that need to work side-by-side). If an ID is omitted, a sequential auto-generated one is used – but this means the order of the elements in the file matters (element A will receive a lower ID than element B if it precedes element B).
If both the client and the server are generated from the same IDL file, then there’s no need to maintain the IDs of elements. However, if older versions of the client have to work with a newer version of the server (or vice versa), then correct provisioning of IDs is crucial. The ID is used to invoke the correct function, and if the ID of an element changes, older clients (using the old ID) will attempt to invoke a wrong (possibly nonexistent) function. It is thus important that elements that remain compatible between versions retain their original ID number.
If a function prototype or semantics are changed, however, it is desired to assign it with a new ID number, so older clients won’t accidentally attempt to invoke it, resulting in unexpected behavior: it is better to receive a ProtocolException saying the ID no longer exists than causing expected behavior.
Here’s an example of how it’s done:
<func name="foo" type="void" id="3900">
<arg name="bar" type="int"/>
</func>
Since Agnos is used to bridge across different languages, each with its own naming convention, it is desired to stick to a single naming convention for uniformity.
If you only use Agnos to interact within the same language, then you can use your target language’s conventions. Otherwise, please use the following ones:
Every IDL element can contain multiple sub-elements named annotation, each taking with two attributes: name and value. This information is practically meaningless to the compiler, but allows you to add additional meta-information to the service, that could be used by other utilities or at runtime (through reflection reflection).
For instance, you may want to mark certain functions as “more privileged” than others, or limit them only to certain users. You could add this metadata as an annotation on the function, for example,
<func name="foo" type="void">
<annotation name="user" value="johns">
<arg name="bar" type="int"/>
</func>
Again, this information is meaningless to the Agnos compiler, but it may be used by your implementation to deny access to any users other than johns, for instance.
The service element is the root element of every IDL specification; it provides the name of the service as well as some other optional information.
<service name="NAME" [versions="VERSIONS"] [package="PACKAGE"] [clientversion="CLIENT_VERSION"]>
Optional.
The name of the package in the generated code. By default it’s the name of the service, but you may want to change it. For example: package="com.foo.bar.toaster"
Optional.
A comma-separated list of versions that this service is compatible with. For example, suppose the first version of toaster was 1.3, and in version 1.4 you added two functions. If version 1.4 is considered compatible with 1.3, you should state so by writing versions="1.3,1.4". If it is not compatible, and is meant to replace version 1.3, you should write versions="1.4".
Note
Versions names do not have to follow any format – they are free-form text. However, it’s expected you stick with the normal versioning conventions.
The order in which versions are specified is important; the oldest compatible version should come first, and the latest compatible version should come last. This is because the last version specified is considered to be the service’s version. For instance, in the case of versions="1.3,1.4", version 1.4 is considered to be the version of the service.
The main purpose of this feature is to allow clients of various versions to connect to a single server. The server, naturally, has a single version – but it may be compatible with multiple ones. This allows older clients to connect to the service.
Version-compatibility is enforced when the client calls assertServiceCompatibility (see Client-Side APIs).
Optional.
The version that the client reports. By default, it’s the service’s version, meaning, the last version specified in the versions attribute.
Note
For proper functioning, client_version must be listed as one of the service’ versions.
Defines a constant value.
<const name="NAME" type="TYPE" value="VALUE" [namespace="NAMESPACE"] />
Required.
The type of the constant. For example: name="float".
Note
Constants may be of the following types: bool, int8, int16, int32, int64, float, and string. All other types are not currently supported.
Required.
The value of the constant. For example: name="3.1415926535". The format of the value is like that of literals in most programming languages:
Optional.
The namespace under which the constant “lives”. This allows you to define two constants with the same name that are contained in different namespaces. The namespace plus the constant’s name form the constant’s fully qualified name.
The format is NAME1[.NAME2[.NAME3[...]]], meaning, a sequence of identifiers separated by periods. If no namespace is provided, the constant is placed in the “root” namespace.
For example:
<const name="RED" type="int" value="7" />
<const name="RED" type="int" value="3" namespace="foo.bar" />
<const name="RED" type="int" value="6" namespace="spam.eggs" />
This will yield the constants RED, foo.bar.RED, and spam.eggs.RED.
Defines an enumeration member. May only be placed within an enum.
<member name="NAME" [value="VALUE"] >
Defines an alias for a type. Note that you may define a typedef of a type before you’ve even defined it.
<typedef name="NAME" type="TYPE" >
Defines a record of fields. Records, unlike classes, pass by-value.
<record name="NAME" [extends="NAME1,NAME2,..."] >
Optional.
A comma-separated list of record names, which this record extends. Note that this is different from the notion of inheritance in object-oriented programming: when record A extends record B, it only means that A defines all the fields that B defined, and perhaps more fields. This is mostly used as a syntactic sugar, but is more meaningful in the context of exceptions. For instance, the following IDL
<record name="Point2D">
<attr name="X" type="float">
<attr name="Y" type="float">
</record>
<record name="Point3D" extends="Point2D">
<attr name="Z" type="float">
</record>
is equivalent to
<record name="Point2D">
<attr name="X" type="float">
<attr name="Y" type="float">
</record>
<record name="Point3D">
<attr name="X" type="float">
<attr name="Y" type="float">
<attr name="Z" type="float">
</record>
Defines an attribute (also known as “field”) within a record.
<attr name="NAME" type="TYPE" />
Defines an exception record. An exception is basically the same as a record, only it inherits the appropriate exception base-class of the target language. Exception, being records, are passed by-value.
Note
Unlike records, exceptions can extend only a single type, which must be an exception on its own.
<exception name="NAME" [extends="NAME"] >
Optional.
In addition to the explanation above, it also generates the expected class-hierarchy. For instance, the following code
<exception name="FooError">
<attr name="message" type="str" />
</exception>
<exception name="BarError" extends="FooError">
<attr name="error_code" type="int" />
</exception>
will generate the exception classes FooError and BarError, such that BarError derives from FooError. This allows for catch-statements to work as expected.
Defines a class, containing attributes and methods. Instances of classes, in contrast to instances of records, pass by-referernce.
<class name="NAME" [extends="NAME1,NAME2,..."] >
Optional.
A comma-separated list of class names, which this class extends, in the normal sense of inheritance in object-oriented programming, allowing for polymorphism. Note that Agnos supports multiple-inheritance (as long as there is no name-collision), since in the implementation, classes are actually interfaces.
For example:
<class name="Animal">
<attr name="name" type="string"/>
<method name="eat" type="void" />
</class>
<class name="Fish" extends="Animal">
<method name="swim" type="void">
<arg name="distance" type="int"/>
</method>
</class>
<class name="Person" extends="Animal">
<method name="walk" type="void">
<arg name="distance" type="int"/>
</method>
</class>
<func name="get_all_living_creatures" type="list[Animal]" />
The function get_all_living_creatures returns a list of Animals, which may be any of Animal, Fish or Person (all up-casted to Animal). You can use down-casting to get the concrete type.
Defines an attribute (also known as “property”) within a class.
<attr name="NAME" type="TYPE" [get="YESNO"] [set="YESNO"] [getid="INT"] [setid="INT"] />
Note
Class attributes are the only elements that do not accept an id attribute. Instead, they accept getid and setid.
Optional.
A boolean value (yes/no or true/false) indicating whether the attribute supports read-access (“getting”). The default is “yes”.
Optional.
A boolean value (yes/no or true/false) indicating whether the attribute supports write-access (“setting”). The default is “yes”.
Optional.
The ID of the getter method (an integer). The default is an auto-generated one. For example getid="3811".
Optional.
The ID of the getter method (an integer). The default is an auto-generated one. For example setid="3812".
Defines a method of a class. Methods are essentially the same as functions, only they take an implicit argument, specifying the object-id on which the operation is performed.
<method name="NAME" type="TYPE" [clientside="YESNO"]>
Required.
The return type of the method, which may be void if the method does not return anything. For example name="string".
Optional.
A boolean (yes/no or true/false) value indicating whether or not this method should be exposed in the generated client. Sometimes a method is deprecated in a later version of the service, but it is desired to keep it available for older clients. Setting this attribute to “no” will cause the relevant code to be generated only on the server-side, but not on the client. This means up-to-date clients will not see it, but older ones will be able to invoke it. The default is “yes”.
A “phantom element”, used only to specify the getid and setid of an inherited attribute. When your class needs to override an inherited attribute and multi-versioning is required, you should use this element to specify the new getid or setid of the overridden attribute.
<inherited-attr name="NAME" [getid="INT"] [setid="INT"] />
A “phantom element”, used only to specify the id of an inherited method. When your class needs to override an inherited method and multi-versioning is required, you should use this element to specify the new id of the overridden attribute.
<inherited-method name="NAME" id="INT" />
A function exposed by the service (also known as “static method”).
Note
function is an alias to func
<func name="NAME" type="TYPE" [clientside="YESNO"] [namespace="NAMESPACE"]>
Required.
The return type of the function, which may be void if the function does not return anything. For example name="list[Person]".
Optional.
A boolean (yes/no or true/false) value indicating whether or not this function should be exposed in the generated client. Sometimes a function is deprecated in a later version of the service, but it is desired to keep it available for older clients. Setting this attribute to “no” will cause the relevant code to be generated only on the server-side, but not on the client. This means up-to-date clients will not see it, but older ones will be able to invoke it. The default is “yes”.
Optional.
The namespace under which the function “lives”. This allows you to define two functions with the same name that are contained in different namespaces. The namespace plus the functions’s name form the function’s fully qualified name.
The format is NAME1[.NAME2[.NAME3[...]]], meaning, a sequence of identifiers separated by periods. If no namespace is provided, the element is placed in the “root” namespace.
For example:
<func name="bark" type="void" />
<func name="bark" type="void" namespace="foo.bar" />
<func name="bark" type="void" namespace="spam.eggs" />
This will yield the functions bark, foo.bar.bark, and spam.eggs.bark.
The following example demonstrates the use of all IDL elements:
<service name="toaster" versions="1.3, 1.4">
<typedef type="float" name="real" />
<const name="pi" type="real" value="3.1415926535" />
<enum name="BreadType">
<member name="White" />
<member name="Whole" />
</enum>
<enum name="Ingredient">
<member name="Cheese" />
<member name="Ham" />
<member name="Olives" />
<member name="Tomato" />
</enum>
<record name="Toast">
<attr name="bread" type="BreadType" />
<attr name="ingredients" type="list[Ingredient]" />
<attr name="dressing" type="string" />
</record>
<exception name="MissingIngredient">
<doc>sorry, but we're freshly out of some ingredient</doc>
<attr name="ingredient" type="Ingredient" />
</exception>
<class name="Toaster">
<method name="makeToast" type="Toast">
</method>
</class>
<enum name="ToasterSize">
<member name="Small" />
<member name="Big" />
</enum>
<func name="get_toaster" type="Toaster" >
<arg name="size" type="ToasterSize" />
</func>
</service>