Configuration¶
Note
With the release of OMERO 5.3.0, the OMERO.insight desktop client has entered maintenance mode, meaning it will only be updated if a major bug is discovered. Instead, the OME team will be focusing on developing the web clients. As a result, coding against this client is no longer recommended.
The container provides a flexible and extensible configuration
mechanism. Each agent has its own configuration file which is parsed at
start-up by the container. The configuration entries in this file are
turned into objects and collected into a map-like object, which is then
passed to the agent. This map object also contains pointers to the
container’s services. Thus, we can think of this object as a Registry.
There is one Registry
for each agent, so configuration entries are
private to each agent - container’s services are shared among all agents
though. The container also has its own configuration file and
Registry
.
The container maintains a set of predefined bindings that are used to
convert a configuration entry into an object - such as a String
,
Integer
, Font
, IconFactory
, etc. However, agents can specify
custom handlers for converting a configuration entry into an object.
Structure¶
Configuration files are XML files which declares only two elements:
<element name="entry" minOccurs="0" maxOccurs="unbounded">
<complexType>
<simpleContent >
<extension base="string">
<attribute name="name" type="string" use="required"/>
<attribute name="type" type="string" default="string"/>
<simpleType>
<restriction base="string">
<enumeration value="string"/>
<enumeration value="integer"/>
<enumeration value="float"/>
<enumeration value="double"/>
<enumeration value="boolean"/>
</restriction>
. . . close all tags
<element name="structuredEntry" minOccurs="0" maxOccurs="unbounded">
<complexType>
<sequence>
<any maxOccurs="unbounded"/>
</sequence>
<attribute name="name" type="string" use="required"/>
<attribute name="type" type="string" default="map"/>
</complexType>
</element>
The entry and structuredEntry elements are used to specify name-value pairs in the actual configuration files. Both elements have a required name attribute whose content is used as a key for accessing the entry value from within the application. In the case of the entry element, the value is a string – this element can thus be used for “classic” name-value style, such as:
<entry name="ITEM">The item's value</entry>
On the other hand, the value of structuredEntry can be made up by any arbitrary sequence of tags.
In both cases (entry and structuredEntry), the entry value is
returned to the application as an object and the type attribute dictates
how the entry value is turned into an object. Only “string”, “integer”,
“float”, “double” and “boolean” may be used for the type attribute
within the entry element, whose content is parsed into a Java
String
, Integer
, Float
, Double
or Boolean
object
according to the content of the type attribute – if this attribute is
missing, then “string” is assumed. For example, say you write the
following into an agent’s configuration file:
<entry name="/some/name" type="boolean">true</entry>
This will make a Boolean
object (set to hold true) available to
the agent – the key for accessing the object will be the string
“/some/name”.
Things work similarly for the structuredEntry element. In this case, the content of the type attribute can be specified to be the fully qualified name of the class that will handle the transformation of the entry value into an object. This is provided so that agents may specify custom handlers for custom configuration entries. For example, an agent’s configuration file could contain the entry:
<structuredEntry name="/some/name" type="some.pkg.SomeHandler">
<tag_1>aValue</tag_1>
<tag_2>anotherValue</tag_2>
</structuredEntry>
In this case, an instance of some.pkg.SomeHandler
will be created to
transform the contents of the entry (that is tag_1 and tag_2) into
a custom object. Obviously enough, the tags contained within a
structuredEntry have to be exactly the tags that the handler expects.
If no type attribute is specified, then it is assumed type = “map”,
which results in the entry’s contents being parsed into a Map
object. Each child tag is assumed to be a simple tag with a string
content, like in the following example:
<structuredEntry name="/some/name">
<key_1>value_1</key_1>
<key_2>value_2</key_2>
</structuredEntry>
Each child tag’s name is a key in the map and the tag’s content is its
value – the above would generate the map:
(key_1, value_1), (key_2, value_2)
.
Some predefined structured entries are supplied by the container for common cases (icons and font entries) and for use by the container only (OMERO and agents entries). Here’s an excerpt from the container’s configuration file:
<container>
<services>
<structuredEntry name="/services/OMERODS" type="OMERODS">
<port>1099</port>
</structuredEntry>
</services>
<agents>
<structuredEntry name="/agents" type="agents">
<agent>
<name>Viewer</name>
<!-- The class tag specifies the FQN of the agent's class. -->
<class>org.openmiscroscopy.shoola.agents.viewer.Viewer</class>
<!-- The config tag specifies the name of the agent‘s configuration file.
This file has to be placed in the config directory under the
installation directory. -->
<config>viewer.xml<config>
</agent>
. . . a similar entry for every other agent
</structuredEntry>
</agents>
<resources>
<iconFactories>
<!-- This entry can be used in agents' configuration files as well.
It is turned into an instance of:
org.openmicroscopy.shoola.env.config.IconFactory
This object can then be used to retrieve any image file within
the directory pointed by the location tag. -->
<structuredEntry name="/resources/icons/DefaultFactory" type="icons">
<!-- The location tag specifies the FQN of the package that contains the icon files. -->
<location>org.openmicroscopy.shoola.env.ui.graphx</location>
</structuredEntry>
. . . more similar entries
</iconFactories>
<fonts>
<!-- This entry can be used in agents' configuration files as well.
It is turned into an instance of java.awt.Font. -->
<structuredEntry name="/resources/fonts/Titles" type="font">
<family>SansSerif</family>
<size>12</size>
<style>bold</style>
</structuredEntry>
. . . more similar entries
</fonts>
</resources>
</container>
The configuration parser only takes the entry and structuredEntry tags into account and ignores all the others. It may be useful to group sets of related entries together, as shown above.
The classes that encompass the machinery for parsing configuration files and building registries are depicted by the following UML class diagram.
The Entry
abstract class sits at the base of a hierarchy of classes
that represent entries in configuration files. It represents a
name-value pair, where the name is the content of the name attribute
of a configuration entry (which is stored by the name
field) and the
value is the object representing the entry’s content. As the logic for
building an object from the entry’s content depends on what is specified
by the type attribute, this class declares an abstract getValue
method which subclasses implement to return the desired object – we use
polymorphism to avoid conditional logic. So we have subclasses
(StringEntry
, IntegerEntry
, IconFactoryEntry
, etc.) to
handle the content of an entry tag (either entry or structuredEntry)
in correspondence of each predefined value of the type attribute
(“string”, “integer”, “icons”, and so on). Given an entry tag, the
createEntryFor
static method (which can be considered a Factory
Method) creates a concrete Entry
object to handle the conversion of
that tag’s content into an object. Subclasses of Entry
implement the
setContent
method to grab the tag’s content, which is then used for
building the object returned by the implementation of getValue()
.
The Registry
Interface declares the operations to be used to access
configuration entries and container’s services.
The RegistryImpl
class implements the Registry
interface. It
maintains a map of Entry
objects, which are keyed by their name
attribute and represent entries in the configuration file. It also
maintains references to the container’s services into member fields –
as services are accessed frequently, this ensures o(1) access time.
The Parser
class is in charge of parsing a configuration file,
extracting entries (only entry and structuredEntry tags are taken
into account), obtain an Entry
object to represent each of those
entries and add these objects to a given RegistryImpl
object.
Dynamics¶
How a configuration file is parsed and the corresponding Registry is built:
A RegistryImpl
object is created with an empty map. Then a
Parser
object is created passing the path to the configuration file
and the RegistryImpl
object. At this point parse()
is invoked on
the Parser
object. The configuration file is read (the XML parsing
is handled by built-in JAXP libraries) and, for each configuration entry
(that is, either entry or struturedEntry tag), createEntryFor()
is called to obtain a concrete Entry
object, which will handle the
conversion of the tag’s content into an object. This Entry
object is
then added to the map kept by the RegistryImpl
object.
In order to find out which class is in charge of handling a given tag,
the Entry
class maintains a map, contentHandlers
, whose keys are
the predefined values used for the type attribute (“string”, “integer”,
“icons”, etc.) and values are the fully qualified names of the handler
classes. Given a tag, createEntryFor()
uses the content of the type
attribute (or “string” if this attribute is missing) to look up the
class name in the map and then creates an instance by reflection - all
Entry
’s subclasses are supposed to have a no-args constructor. If
the class name is not found in the map, then the content of the type
attribute is assumed to be a valid fully qualified name of an
Entry
’s subclass. This allows for agents to specify custom handlers
– as long as the handler extends Entry
and has a public no-args
constructor.
Notice that the RegistryImpl
object adds the couple
(e.getName(), e)
to its map. Because the Entry
class takes care
of setting the name field to the content of the name attribute within
the entry tag, the application code can subsequently access e by
specifying the value of the name attribute to lookup()
. The above
outlined process is repeated for each configuration file so that the
configuration entries of each agent (and the container) are kept in
separate objects – a RegistryImpl
is created every time. Because
every agent is then provided with its own RegistryImpl
object, the
configuration entries are private to each agent. However, the container
configures all RegistryImpl
objects with the same references to its
services.
See also