Many parts of a Geode cache contain configuration details that are persisted through server restarts or applied to new servers upon startup, such as region attributes and async event queues. If a developer wants to add persistence to a part of the cache that does not currently have any persisted configuration, one way to do so is via the CacheService
interface. Services created using this interface are loaded during cache initialization using the Java ServiceLoader and can be retrieved from the cache by calling Cache.getService(*YOUR_INTERFACE.CLASS*)
.
The service is initialized with some default state and can then be configured during the CacheCreation.create()
method using configuration details parsed from XML. The current state of the service can also be persisted to XML when it is modified via gfsh commands. The following steps outline how to achieve this.
Step-by-step guide:
Create an .xsd file in resources/META-INF/schemas/geode.apache.org/schema/*PACKAGE_NAME*/*SCHEMA_NAME*.xsd,
specifying the targetNamespace
as geode.apache.org/schema/*PACKAGE_NAME*
, and define the elements of your service that you wish to persist.
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
targetNamespace="http://geode.apache.org/schema/*PACKAGE_NAME*"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0">
***SCHEMA_ELEMENTS_DEFINITION***
</xsd:schema>
Create an XML parser for your service which extends AbstractXmlParser
. The getNamespaceUri()
method should return the same namespace that was defined as the targetNamespace
in your schema. The startElement()
and endElement()
methods should be implemented to process each element of your schema by interacting with the AbstractXmlParser.stack
field via peek()
, pop()
and push()
to navigate through the parsed XML elements. This can be implemented directly in your XML parser class (see LuceneXmlParser
) or by delegating the process to an ElementType
enum with enumerators for each element defined in your XSD schema (see JdbcConnectorServiceXmlParser
). You may also need to create data structure classes that hold the configuration information parsed from the XML until the cache has been created and the service you want to configure exists. These classes are typically named *Creation
. Ultimately you will be adding an object/Creation generated from your top-level XML element to the CacheCreation
class and configuring your service in the CacheCreation.create()
method using the information contained in that object.
public class ExampleXmlParser extends AbstractXmlParser {
public static final String NAMESPACE = "http://geode.apache.org/schema/*PACKAGE_NAME*"
public static final String *PARENT_NAME* = *SCHEMA_ELEMENT_NAME*
public static final String *NESTED_NAME* = *NESTED_SCHEMA_ELEMENT_NAME*
@Override
public String getNamespaceUri() {
return NAMESPACE;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
if (!NAMESPACE.equals(uri)) {
return;
}
if (*PARENT_NAME*.equals(localName)) {
startParent(atts);
}
if (*NESTED_NAME*.equals(localName)) {
startNested(atts);
}
}
private void startParent(Attributes atts) {
if (!(stack.peek() instanceof CacheCreation)) {
throw new CacheXmlException("<" + *PARENT_NAME* + "> elements must occur within <cache> elements");
}
**create and initialize your parent Creation object and push it to the stack**
}
private void startNested(Attributes atts) {
if (!(stack.peek() instanceof *PARENT_OBJECT_CREATION*)) {
throw new CacheXmlException("<" + *NESTED_NAME* + "> elements must occur within <" + *PARENT_NAME* + "> elements");
}
**create and initialize your nested object and add it to its parent Creation object**
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (!NAMESPACE.equals(uri)) {
return;
}
if (*PARENT_NAME*.equals(localName)) {
endParent();
}
if (*NESTED_NAME*.equals(localName)) {
endNested();
}
}
private void endParent() {
**pop your object off the top of the stack, then add it to the CacheCreation object retrieved using stack.peek()**
}
private void endNested() {
// Do nothing, since the nested object has already been added to
// the parent object Creation
}
public class ExampleXmlParser extends AbstractXmlParser {
public static final String NAMESPACE = "http://geode.apache.org/schema/*PACKAGE_NAME*"
public static final String *PARENT_NAME* = *SCHEMA_ELEMENT_NAME*
public static final String *NESTED_NAME* = *NESTED_SCHEMA_ELEMENT_NAME*
@Override
public String getNamespaceUri() {
return NAMESPACE;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (!NAMESPACE.equals(uri)) {
return;
}
ElementType.getTypeFromName(localName).startElement(stack, attributes);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (!NAMESPACE.equals(uri)) {
return;
}
ElementType.getTypeFromName(localName).endElement(stack);
}
public enum ElementType {
*PARENT_ELEMENT*(ExampleXmlParser.*PARENT_NAME*) {
@Override
void startElement(Stack<Object> stack, Attributes attributes) {
if (!(stack.peek() instanceof CacheCreation)) {
throw new CacheXmlException("<" + *PARENT_NAME* + "> elements must occur within <cache> elements");
}
**create and initialize your parent Creation object and push it to the stack**
}
@Override
void endElement(Stack<Object> stack) {
**pop your object off the top of the stack, then add it to the CacheCreation object retrieved using stack.peek()**
}
},
*NESTED_ELEMENT*(ExampleXmlParser.*NESTED_NAME*) {
@Override
void startElement(Stack<Object> stack, Attributes attributes) {
if (!(stack.peek() instanceof *PARENT_OBJECT_CREATION*)) {
throw new CacheXmlException("<" + *NESTED_NAME* + "> elements must occur within <" + *PARENT_NAME* + "> elements");
}
**create and initialize your nested object and add it to its parent Creation object**
}
@Override
void endElement(Stack<Object> stack) {
// Do nothing, since the nested object has already been added to
// the parent object Creation
}
};
private String typeName;
ElementType(String typeName) {
this.typeName = typeName;
}
static ElementType getTypeFromName(String typeName) {
for (ElementType type : ElementType.values()) {
if (type.typeName.equals(typeName))
return type;
}
throw new IllegalArgumentException("Invalid type '" + typeName + "'");
}
public String getTypeName() {
return typeName;
}
abstract void startElement(Stack<Object> stack, Attributes attributes);
abstract void endElement(Stack<Object> stack);
}
- Create a file named
org.apache.geode.internal.cache.xmlcache.XmlParser
in the resources/META-INF/services
package. The file should contain the fully qualified class name of your XML parser. If a file with that name already exists, add the class name of your XML parser to it.
- Create an interface that extends
CacheService
and will serve as the interface for your service. It should have methods that take the objects created and added to the CacheCreation
class in step 2 and use the information contained in them to configure the service.
- Implement the interface just created. Ensure that the overridden
getInterface()
method returns the interface created in step 4.
- Create a file named
org.apache.geode.internal.cache.CacheService
in the resources/META-INF/services
package. The file should contain the fully qualified class name of the class implemented in step 5 (not the interface). If a file with that name already exists, add the class name of your cache service to it.
In the CacheCreation.create()
method, use the cache.getService(*YOUR_SERVICE_INTERFACE*.class)
method to retrieve your service implementation and configure it using the objects created and added to CacheCreation
in step 2. This completes the steps required to allow the service to be created from XML. To allow gfsh commands to update and persist the configuration of your service using the updateConfigForGroup
method, the following steps are also required.
public class CacheCreation implements InternalCache {
...
private *YOUR_CREATION_CLASS* *YOUR_CREATION*
getter()
setter()
...
create(InternalCache cache) {
...
if (*YOUR_CREATION* != null) {
*YOUR_SERVICE* service =
(*YOUR_SERVICE*) cache.getService(*YOUR_SERVICE_INTERFACE*.class);
if (service != null) {
**configure your service using the information stored in your creation**
}
}
...
}
}
- Create an annotated class in the
*YOUR_PACKAGE*.management.configuration
package based on your xsd schema. This can be done manually (see RegionMapping
and FieldMapping
) or by using the JAXB plugin for IntelliJ (while in your .xsd file, select Tools → JAXB → Generate Java Code from Xml Schema using JAXB… and then follow the instructions, making sure to specify the correct output path and package prefix).
In your annotated class, add the @XSDRootElement
annotation above the class definition and make the class extend CacheElement
. You will need to implement the getId()
method to return an appropriate identifier for this element:
@XSDRootElement(namespace = "http://geode.apache.org/schema/*PACKAGE_NAME*",
schemaLocation = "http://geode.apache.org/schema/*PACKAGE_NAME*/*SCHEMA_NAME*.xsd")
public class *YOUR_ANNOTATED_CLASS* extends CacheElement {
@Override
public String getId() {
return *IDENTIFIER*;
}
And, in the same location as your annotated class, create a package-info.java
file containing the following:
@XmlSchema(namespace = "http://geode.apache.org/schema/*PACKAGE_NAME*",
xmlns = {@XmlNs(prefix = "*ELEMENT_PREFIX*",
namespaceURI = "http://geode.apache.org/schema/*PACKAGE_NAME*")},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
where *ELEMENT_PREFIX*
is the prefix to be applied to XML elements that are defined by your schema.
Final steps: make appropriate changes to excludedClasses.txt
, sanctioned-geode-core-serializables.txt
and assembly_content.txt
to allow integration tests to pass. The easiest way to do this is to let the tests run using the following commands and follow the instructions provided by them when/if they fail:
$ ./gradlew geode-core:integrationTest --tests AnalyzeSerializablesJUnitTest
$ ./gradlew geode-assembly:integrationTest --tests AssemblyContentsIntegrationTest
Related articles
-
Page:
-
Page:
-
Page:
-
Page:
-
Page: