blob: ac0c13151ad671bd184ba113a2ae910385267b76 [file] [log] [blame]
type=page
status=published
title=Adding Configuration Data for a Component
next=adding-container-capabilities.html
prev=adding-monitoring-capabilities.html
~~~~~~
= Adding Configuration Data for a Component
[[GSACG00006]][[gjlpe]]
[[adding-configuration-data-for-a-component]]
== Adding Configuration Data for a Component
The configuration data of a component determines the characteristics and
runtime behavior of a component. {productName} provides interfaces to
enable an add-on component to store its configuration data in the same
way as other {productName} components. These interfaces are similar
to interfaces that are defined in
[Jakarta XML Binding](`https://jakarta.ee/specifications/xml-binding/`). By using these interfaces to
store configuration data, you ensure that the add-on component is fully
integrated with {productName}. As a result, administrators can
configure an add-on component in the same way as they can configure
other {productName} components.
The following topics are addressed here:
* link:#gjrdv[How {productName} Stores Configuration Data]
* link:#gjlop[Defining an Element]
* link:#gjlpb[Defining an Attribute of an Element]
* link:#gjlov[Defining a Subelement]
* link:#gjlpu[Validating Configuration Data]
* link:#gjrdj[Initializing a Component's Configuration Data]
* link:#gjrcz[Creating a Transaction to Update Configuration Data]
* link:#gjmkt[Dotted Names and REST URLs of Configuration Attributes]
* link:#gkaal[Examples of Adding Configuration Data for a Component]
[[gjrdv]][[GSACG00121]][[how-glassfish-server-stores-configuration-data]]
=== How {productName} Stores Configuration Data
{productName} stores the configuration data for a domain in a single
configuration file that is named `domain.xml`. This file is an
extensible markup language (XML) instance that contains a hierarchy of
elements to represent a domain's configuration. The content model of
this XML instance is not defined in a document type definition (DTD) or
an XML schema. Instead, the content model is derived from Java language
interfaces with appropriate annotations. You use these annotations to
add configuration data for a component as explained in the sections that
follow.
[[gjlop]][[GSACG00122]][[defining-an-element]]
=== Defining an Element
An element represents an item of configuration data. For example, to
represent the configuration data for a network listener, {productName} defines the `network-listener` element.
Define an element for each item of configuration data that you are
adding.
[[gjcnt]][[GSACG00074]][[to-define-an-element]]
==== To Define an Element
1. Define a Java language interface to represent the element.
Define one interface for each element. Do not represent multiple
elements in a single interface.
The name that you give to the interface determines name of the element
as follows:
* A change from lowercase to uppercase in the interface name is
transformed to the hyphen (`-`) separator character.
* The element name is all lowercase.
For example, to define an interface to represent the
`wombat-container-config` element, give the name `WombatContainerConfig`
to the interface.
2. Specify the parent of the element.
To specify the parent, extend the interface that identifies the parent
as shown in the following table.
+
[width="100%",cols="<27%,<73%",options="header",]
|===
|Parent Element |Interface to Extend
|`config`
|`org.glassfish.api.admin.config.Container`
|`applications`
|`org.glassfish.api.admin.config.ApplicationName`
|Another element that you are defining
|`org.jvnet.hk2.config.ConfigBeanProxy`
|===
3. Annotate the declaration of the interface with the
`org.jvnet.hk2.config.Configured` annotation.
[[GSACG00053]][[gjcne]]
Example 6-1 Declaration of an Interface That Defines an Element
This example shows the declaration of the `WombatContainerConfig`
interface that represents the `wombat-container-config` element.
The parent of this element is the `config` element.
[source,java]
----
...
import org.jvnet.hk2.config.Configured;
...
import org.glassfish.api.admin.config.Container;
...
@Configured
public interface WombatContainerConfig extends Container {
...
}
----
[[sthref6]]
How Interfaces That Are Annotated With @Configured Are Implemented
You are not required to implement any interfaces that you annotate with
the `@Configured` annotation. {productName} implements these
interfaces by using the `Dom` class. {productName} creates a proxy
for each `Dom` object to implement the interface.
[[gjlpb]][[GSACG00123]][[defining-an-attribute-of-an-element]]
=== Defining an Attribute of an Element
The attributes of an element describe the characteristics of the
element.
For example, the `port` attribute of the `network-listener`
element identifies the port number on which the listener listens.
[[gjorj]][[GSACG00227]][[representing-an-attribute-of-an-element]]
==== Representing an Attribute of an Element
Represent each attribute of an element as the property of a pair of
JavaBeans specification getter and setter methods of the interface that
defines the element. The component for which the configuration data is
being defined can then access the attribute through the getter method.
The setter method enables the attribute to be updated.
[[gjopa]][[GSACG00228]][[specifying-the-data-type-of-an-attribute]]
==== Specifying the Data Type of an Attribute
The data type of an attribute is the return type of the getter method
that is associated with the attribute. To enable the attribute take
properties in the form `${`property-name`}` as values, specify the data
type as `String`.
[[gjopm]][[GSACG00229]][[identifying-an-attribute-of-an-element]]
==== Identifying an Attribute of an Element
To identify an attribute of an element, annotate the declaration of the
getter method that is associated with the attribute with the
`org.jvnet.hk2.config.Attribute` annotation.
To specify the properties of the attribute, use the elements of the
`@Attribute` annotation as explained in the sections that follow.
[[gjopq]][[GSACG00230]][[specifying-the-name-of-an-attribute]]
==== Specifying the Name of an Attribute
To specify the name of an attribute, set the `value` element of the
`@Attribute` annotation to a string that specifies the name. If you do
not set this element, the name is derived from the name of the property
as follows:
* A change from lowercase to uppercase in the interface name is
transformed to the hyphen (`-`) separator character.
* The element name is all lowercase.
For example, if the getter method `getNumberOfInstances` is defined for
the property `NumberOfInstances` to represent an attribute, the name of
the attribute is `number-of-instances`.
[[gjoqj]][[GSACG00231]][[specifying-the-default-value-of-an-attribute]]
==== Specifying the Default Value of an Attribute
The default value of an attribute is the value that is applied if the
attribute is omitted when the element is written to the domain
configuration file.
To specify the default value of an attribute, set the `defaultValue`
element of the `@Attribute` annotation to a string that contains the
default value. If you do not set this element, the parameter has no
default value.
[[gjoui]][[GSACG00232]][[specifying-whether-an-attribute-is-required-or-optional]]
==== Specifying Whether an Attribute Is Required or Optional
Whether an attribute is required or optional determines how {productName} responds if the parameter is omitted when the element is written
to the domain configuration file:
* If the attribute is required, an error occurs.
* If the attribute is optional, the element is written successfully to
the domain configuration file.
To specify whether an attribute is required or optional, set the
`required` element of the `@Attribute` annotation as follows:
* If the attribute is required, set the `required` element to `true`.
* If the attribute is optional, set the `required` element to `false`.
This value is the default.
[[gjrdz]][[GSACG00233]][[example-of-defining-an-attribute-of-an-element]]
==== Example of Defining an Attribute of an Element
[[sthref7]]
Example 6-2 Defining an Attribute of an Element
This example defines the attribute `number-of-instances`. To enable the
attribute take properties in the form `${`property-name`}` as values,
the data type of this attribute is `String`.
[source,java]
----
import org.jvnet.hk2.config.Attribute;
...
@Attribute
public String getNumberOfInstances();
public void setNumberOfInstances(String instances) throws PropertyVetoException;
...
----
[[gjlov]][[GSACG00124]][[defining-a-subelement]]
=== Defining a Subelement
A subelement represents a containment or ownership relationship. For
example, {productName} defines the `network-listeners` element to
contain the configuration data for individual network listeners. The
configuration data for an individual network listener is represented by
the `network-listener` element, which is a subelement of
`network-listeners` element.
[[gjzlb]][[GSACG00075]][[to-define-a-subelement]]
==== To Define a Subelement
1. Define an interface to represent the subelement.
For more information, see link:#gjlop[Defining an Element].
The interface that represents the subelement must extend the
`org.jvnet.hk2.config.ConfigBeanProxy` interface.
2. In the interface that defines the parent element, identify the
subelement to its parent element.
3. Represent the subelement as the property of a JavaBeans
specification getter or setter method.
4. Annotate the declaration of the getter or setter method that is
associated with the subelement with the `org.jvnet.hk2.config.Element`
annotation.
[[GSACG00054]][[gjzjt]]
Example 6-3 Declaring an Interface to Represent a Subelement
This example shows the declaration of the `WombatElement` interface to
represent the `wombat-element` element.
[source,java]
----
...
import org.jvnet.hk2.config.ConfigBeanProxy;
import org.jvnet.hk2.config.Configured;
...
@Configured
public interface WombatElement extends ConfigBeanProxy {
...
}
...
----
[[GSACG00055]][[gjzkh]]
Example 6-4 Identifying a Subelement to its Parent Element
This example identifies the `wombat-element` element as a subelement.
[source,java]
----
...
import org.jvnet.hk2.config.Element;
...
import java.beans.PropertyVetoException;
...
@Element
public WombatElement getElement();
public void setElement(WombatElement element) throws PropertyVetoException;
...
----
[[gjlpu]][[GSACG00125]][[validating-configuration-data]]
=== Validating Configuration Data
Validating configuration data ensures that attribute values that are
being set or updated do not violate any constraints that you impose on
the data. For example, you might require that an attribute that
represents a name is not null, or an integer that represents a port
number is within the range of available port numbers. Any attempt to set
or update an attribute value that fails validation fails. Any
validations that you specify for an attribute are performed when the
attribute is initialized and every time the attribute is changed.
To standardize the validation of configuration data, {productName}
uses https://jakarta.ee/specifications/bean-validation/[Jakarta Bean Validation]
(`https://jakarta.ee/specifications/bean-validation/`) for validating configuration
data.
Jakarta Bean Validation defines a metadata model and API for the validation of
JavaBeans components.
To validate an attribute of an element, annotate the attribute's getter
method with the annotation in the `jakarta.validation.constraints` package
that performs the validation that you require. The following table lists
commonly used annotations for validating {productName} configuration
data.
For the complete list of annotations, see the
https://jakarta.ee/specifications/bean-validation/3.0/apidocs/jakarta/validation/constraints/package-summary.html[`jakarta.validation.constraints`
package summary]
(`https://jakarta.ee/specifications/bean-validation/3.0/apidocs/jakarta/validation/constraints/package-summary.html`).
[[sthref8]][[gjrlg]]
Table 6-1 Commonly Used Annotations for Validating {productName}
Configuration Data
[width="100%",cols="<37%,<63%",options="header",]
|===
|Validation |Annotation
|Not null |`jakarta.validation.constraints.NotNull`
|Null |`jakarta.validation.constraints.Null`
|Minimum value a|
`jakarta.validation.constraints.Min`
Set the `value` element of this annotation to the minimum allowed value.
|Maximum value a|
`jakarta.validation.constraints.Max`
Set the `value` element of this annotation to the maximum allowed value.
|Regular expression matching a|
`jakarta.validation.constraints.Pattern`
Set the `regexp` element of this annotation to the regular expression
that is to be matched.
|===
[[GSACG00056]][[gjrmp]]
Example 6-5 Specifying a Range of Valid Values for an Integer
This example specifies that the attribute `rotation-interval-in-minutes`
must be a positive integer.
[source,java]
----
...
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
...
@Min(value=1)
@Max(value=Integer.MAX_VALUE)
String getRotationIntervalInMinutes();
...
----
[[GSACG00057]][[gjzkq]]
Example 6-6 Specifying Regular Expression Matching
This example specifies that the attribute `classname` must contain only
non-whitespace characters.
[source,java]
----
import jakarta.validation.constraints.Pattern;
...
@Pattern(regexp="^[\\S]*$")
String getClassname();
...
----
[[gjrdj]][[GSACG00126]][[initializing-a-components-configuration-data]]
=== Initializing a Component's Configuration Data
To ensure that a component's configuration data is added to the
`domain.xml` file when the component is first instantiated, you must
initialize the component's configuration data.
Initializing a component's configuration data involves the following
tasks:
* link:#gkbgi[To Define a Component's Initial Configuration Data]
* link:#gjses[To Write a Component's Initial Configuration Data to the
`domain.xml` File]
[[gkbgi]][[GSACG00076]][[to-define-a-components-initial-configuration-data]]
==== To Define a Component's Initial Configuration Data
1. Create a plain-text file that contains an XML fragment to represent
the configuration data.
* Ensure that each XML element accurately represents the interface that
is defined for the element.
* Ensure that any subelements that you are initializing are correctly nested.
* Set attributes of the elements to their required initial values.
2. When you package the component, include the file that contains the
XML fragment in the component's JAR file.
[[GSACG00058]][[gkaba]]
Example 6-7 XML Data Fragment
This example shows the XML data fragment for adding the
`wombat-container-config` element to the `domain.xml` file. The
`wombat-container-config` element contains the subelement
`wombat-element`. The attributes of `wombat-element` are initialized as
follows:
* The `foo` attribute is set to `something`.
* The `bar` attribute is set to `anything`.
[source,xml]
----
<wombat-container-config>
<wombat-element foo="something" bar="anything"/>
</wombat-container-config>
----
[[gjses]][[GSACG00077]][[to-write-a-components-initial-configuration-data-to-the-domain.xml-file]]
==== To Write a Component's Initial Configuration Data to the `domain.xml` File
Add code to write the component's initial configuration data in the
class that represents your add-on component. If your add-on component is
a container, add this code to the sniffer class. For more information
about adding a container, see
link:adding-container-capabilities.html#ghmon[Adding Container
Capabilities].
1. Set an optional dependency on an instance of the class that
represents the XML element that you are adding.
2. Initialize the instance variable to `null`.
+
If the element is not present in the `domain.xml` file when the add-on
component is initialized, the instance variable remains `null`.
3. Annotate the declaration of the instance variable with the
`org.jvnet.hk2.annotations.Inject` annotation.
4. Set the `optional` element of the `@Inject` annotation to `true`.
5. Set a dependency on an instance of the following classes:
* `org.glassfish.api.admin.config.ConfigParser`
+
The `ConfigParser` class provides methods to parse an XML fragment and
to write the fragment to the correct location in the `domain.xml` file.
* `org.jvnet.hk2.component.Habitat`
6. Invoke the `parseContainerConfig` method of the `ConfigParser`
object only if the instance is `null`.
If your add-on component is a container, invoke this method within the
implementation of the `setup` method the sniffer class.
When the container is first instantiated, {productName} invokes the `setup` method.
The test that the instance is `null` is required to ensure that the
configuration data is added only if the data is not already present in
the `domain.xml` file.
In the invocation of the `parseContainerConfig` method, pass the
following items as parameters:
* The `Habitat` object on which you set a dependency
* The URL to the file that contains the XML fragment that represents the configuration data
[[GSACG00059]][[gkabo]]
Example 6-8 Writing a Component's Initial Configuration Data to the
`domain.xml` File
This example writes the XML fragment in the file `init.xml` to the
`domain.xml` file. The fragment is written only if the `domain.xml` file
does not contain the `wombat-container-config-element`.
The `wombat-container-config` element is represented by the
`WombatContainerConfig` interface. An optional dependency is set on an
instance of a class that implements `WombatContainerConfig`.
[source,java]
----
...
import org.glassfish.api.admin.config.ConfigParser;
import org.glassfish.examples.extension.config.WombatContainerConfig;
...
import org.jvnet.hk2.annotations.Inject;
import org.jvnet.hk2.component.Habitat;
import com.sun.enterprise.module.Module;
import java.util.logging.Logger;
...
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.net.URL;
...
@Inject(optional=true)
WombatContainerConfig config=null;
...
@Inject
ConfigParser configParser;
@Inject
Habitat habitat;
public Module[] setup(String containerHome, Logger logger) throws IOException {
if (config==null) {
URL url = this.getClass().getClassLoader().getResource("init.xml");
if (url!=null) {
configParser.parseContainerConfig(habitat, url,
WombatContainerConfig.class);
}
}
return null;
}
...
----
[[GSACG00060]][[gjski]]
Example 6-9 `domain.xml` File After Initialization
This example shows the `domain.xml` file after the `setup` method was
invoked to add the `wombat-container-config` element under the `config`
element.
[source,xml]
----
<domain>
...
<configs>
<config name="server-config">
<wombat-container-config number-of-instances="5">
<wombat-element foo="something" bar="anything" />
</wombat-container-config>
<http-service>
...
</domain>
----
[[gjrcz]][[GSACG00127]][[creating-a-transaction-to-update-configuration-data]]
=== Creating a Transaction to Update Configuration Data
Creating a transaction to update configuration data enables the data to
be updated without the need to specify a dotted name in the
link:reference-manual/set.html#GSRFM00226[`set`] subcommand. You can make the transaction
available to system administrators in the following ways:
* By adding an link:reference-manual/asadmin.html#GSRFM00263[`asadmin`] subcommand. If you are adding
an `asadmin` subcommand, include the code for the transaction in the
body of the subcommand's `execute` method. For more information, see
link:extending-asadmin.html#ghmrd[Extending the `asadmin` Utility].
* By extending the Administration Console. For more information, see
link:extending-the-admin-console.html#ghmrb[Extending the Administration
Console].
[[gkakw]][[GSACG00078]][[to-create-a-transaction-to-update-configuration-data]]
==== To Create a Transaction to Update Configuration Data
Any transaction that you create to modify configuration data must use a
configuration change transaction to ensure that the change is atomic,
consistent, isolated, and durable (ACID).
. [[gkakq]]
Set a dependency on the configuration object to update.
. [[gkalq]]
Define a method to invoke to perform the transaction.
. Use the generic `SimpleConfigCode` interface to define the method
that is to be invoked on a single configuration object, namely:
`SingleConfigCode<T extends ConfigBeanProxy>()`.
. In the body of this method, implement the `run` method of the
`SingleConfigCode<T extends ConfigBeanProxy>` interface.
. In the body of the `run` method, invoke the setter methods that are
defined for the attributes that you are setting.
+
These setter methods are defined in the interface that represents the
element whose elements you are setting.
. Invoke the static method
`org.jvnet.hk2.config.ConfigSupport.ConfigSupport.apply`.
In the invocation, pass the following information as parameters to the method:
* The code of the method that you defined in StepĀ  link:#gkalq[2].
* The configuration object to update, on which you set the dependency in StepĀ link:#gkakq[1].
[[sthref9]]
Example 6-10 Creating a Transaction to Update Configuration Data
This example shows code in the `execute` method of an `asadmin`
subcommand for updating the `number-of-instances` element of
`wombat-container-config` element.
[source,java]
----
...
import org.glassfish.api.Param;
...
import org.jvnet.hk2.annotations.Inject;
import org.jvnet.hk2.config.Transactions;
import org.jvnet.hk2.config.ConfigSupport;
import org.jvnet.hk2.config.SingleConfigCode;
import org.jvnet.hk2.config.TransactionFailure;
...
@Param
String instances;
@Inject
WombatContainerConfig config;
public void execute(AdminCommandContext adminCommandContext) {
try {
ConfigSupport.apply(new SingleConfigCode<WombatContainerConfig>() {
public Object run(WombatContainerConfig wombatContainerConfig)
throws PropertyVetoException, TransactionFailure {
wombatContainerConfig.setNumberOfInstances(instances);
return null;
}
}, config);
} catch(TransactionFailure e) {
}
}
...
----
[[gjmkt]][[GSACG00128]][[dotted-names-and-rest-urls-of-configuration-attributes]]
=== Dotted Names and REST URLs of Configuration Attributes
The {productName} administrative subcommands link:reference-manual/get.html#GSRFM00139[`get`],
link:reference-manual/list.html#GSRFM00145[`list`], and olink:GSRFM00226[`set`] locate a
configuration attribute through the dotted name of the attribute. The
dotted name of an attribute of a configuration element is as follows:
[source]
----
configs.config.server-config.element-name[.subelement-name...].attribute-name
----
element-name::
The name of an element that contains a subelement or the attribute.
subelement-name::
The name of a subelement, if any.
attribute-name::
The name of the attribute.
+
For example, the dotted name of the `foo` attribute of the
`wombat-element` element is as follows:
+
[source]
----
configs.config.server-config.wombat-container-config.wombat-element.foo
----
+
The formats of the URL to a REST resource that represent an attribute of
a configuration element is as follows:
+
[source]
----
http://host:port/management/domain/path
----
host::
The host where the DAS is running.
port::
The HTTP port or HTTPS port for administration.
path::
The path to the attribute. The path is the dotted name of the
attribute in which each dot (`.`) is replaced with a slash (`/`).
For example, the URL to the REST resource for the `foo` attribute of the
`wombat-element` element is as follows:
[source]
----
http://localhost:4848/management/domain/configs/config/server-config/wombat-container-config/wombat-element/foo
----
In this example, the DAS is running on the local host and the HTTP port
for administration is 4848.
[[gkaal]][[GSACG00131]][[examples-of-adding-configuration-data-for-a-component]]
=== Examples of Adding Configuration Data for a Component
This example shows the interfaces that define the configuration data for
the Greeter Container component. The data is comprised of the following
elements:
* A parent element, which is shown in link:#gkamy[Example 6-11]
* A subelement that is contained by the parent element, which is shown
in link:#gkamb[Example 6-12]
This example also shows an XML data fragment for initializing an
element. See link:#gkamk[Example 6-13].
Code for the Greeter Container component is shown in
link:adding-container-capabilities.html#gkane[Example of Adding Container
Capabilities].
Code for an `asadmin` subcommand that updates the configuration data in
this example is shown in link:extending-asadmin.html#gkbdf[Example 4-7].
[[GSACG00061]][[gkamy]]
Example 6-11 Parent Element Definition
This example shows the definition of the `greeter-container-config` element.
The attributes of the `greeter-container-config` element are as follows:
* `number-of-instances`, which must be in the range 1-10.
* `language`, which must contain only non-whitespace characters.
* `style`, which must contain only non-whitespace characters.
The `greeter-element` element is identified as a subelement of the
`greeter-container-config` element. The definition of the
`greeter-element` element is shown in link:#gkamb[Example 6-12].
[source,java]
----
package org.glassfish.examples.extension.greeter.config;
import org.jvnet.hk2.config.Configured;
import org.jvnet.hk2.config.Attribute;
import org.jvnet.hk2.config.Element;
import org.glassfish.api.admin.config.Container;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Max;
import java.beans.PropertyVetoException;
@Configured
public interface GreeterContainerConfig extends Container {
@Attribute
@Min(value=1)
@Max (value=10)
public String getNumberOfInstances();
public void setNumberOfInstances(String instances) throws PropertyVetoException;
@Attribute
@Pattern(regexp = "^[\\S]*$")
public String getLanguage();
public void setLanguage(String language) throws PropertyVetoException;
@Attribute
@Pattern(regexp = "^[\\S]*$")
public String getStyle();
public void setStyle(String style) throws PropertyVetoException;
@Element
public GreeterElement getElement();
public void setElement(GreeterElement element) throws PropertyVetoException;
}
----
[[GSACG00062]][[gkamb]]
Example 6-12 Subelement Definition
This example shows the definition of the `greeter-element` element,
which is identified as a subelement of the `greeter-container-config`
element in link:#gkamy[Example 6-11]. The only attribute of the
`greeter-element` element is `greeter-port`, which must be in the range 1030-1050.
[source,java]
----
package org.glassfish.examples.extension.greeter.config;
import org.jvnet.hk2.config.ConfigBeanProxy;
import org.jvnet.hk2.config.Configured;
import org.jvnet.hk2.config.Attribute;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Max;
import java.beans.PropertyVetoException;
@Configured
public interface GreeterElement extends ConfigBeanProxy {
@Attribute
@Min(value=1030)
@Max (value=1050)
public String getGreeterPort();
public void setGreeterPort(String greeterport) throws PropertyVetoException;
}
----
[[GSACG00063]][[gkamk]]
Example 6-13 XML Data Fragment for Initializing the `greeter-container-config` Element
This example shows the XML data fragment for adding the
`greeter-container-config` element to the `domain.xml` file.
The `greeter-container-config` element contains the subelement `greeter-element`.
The attributes of `greeter-container-config` are initialized as follows:
* The `number-of-instances` attribute is set to `5`.
* The `language` attribute is set to `norsk`.
* The `style` element is set to `formal`.
The `greeter-port` attribute of the `greeter-element` element is set to `1040`.
[source,xml]
----
<greeter-container-config number-of-instances="5" language="norsk" style="formal">
<greeter-element greeter-port="1040"/>
</greeter-container-config>
----
The definition of the `greeter-container-config` element is shown in
link:#gkamy[Example 6-11]. The definition of the `greeter-element`
element is shown in link:#gkamb[Example 6-12].