18 Extending Cache Configuration Files

This chapter provides detailed instructions for extending Coherence cache configuration files using XML namespaces and namespace handler classes. The instructions in this chapter assume a general understanding of XML namespaces and XML processing.

This chapter includes the following sections:

Introduction to Extending Cache Configuration Files

Cache configuration files can include user-defined XML elements and attributes. The elements and attributes are declared within an XML namespace and are processed by a namespace handler at runtime. The namespace handler allows application logic to be executed based on the processing of the elements and attributes.

The following example extends a cache configuration file by declaring a run namespace that is associated with a RunNamespaceHandler namespace handler class. At runtime, the handler class processes the <run:runnable> element and its attributes and executes any logic on the cluster member as required.

<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/
                 coherence-cache-config coherence-cache-config.xsd"
              xmlns:run="class://com.examples.RunNamespaceHandler">
   <run:runnable classname="MyRunnable" every="10m"/>
   ...
</cache-config>

Cache configuration files are typically extended to allow applications to perform custom initialization, background tasks, or perform monitoring and maintenance of caches in a cluster. For example, an application can:

  • establish domain-specific cache entry indexes

  • preload cached information

  • load configuration into a cluster

  • run or schedule background tasks against the cluster

  • integrate with external systems

Extending cache configuration files offers applications a common and consolidated place for configuration. In addition, application logic is embedded and managed in a cluster, which is a high-availability and scalable environment that can provide automated recovery of failed application logic if required.

Declaring XML Namespaces

Namespaces are declared in a cache configuration file by using a namespace declaration. The use of XML namespaces must adhere to the XML specification. At runtime, the XML syntax and XML namespaces are validated and checks are performed to ensure that the namespace prefixes have a corresponding xmlns declaration. Errors that are encountered in the cache configuration file results in the Coherence member failing to start. The following example declares a namespace that uses the prefix ex:

<?xml version="1.0"?>

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
   xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config
      coherence-cache-config.xsd"
   xmlns:ex="URI">
   ...

The URI value for a namespace must be in the format class://FullyQualifiedClassName. If an incorrect format is provided, then a failure occurs and the Coherence member fails to start. The handler class provided in the declaration must be a class that implements the NamespaceHandler interface. For details about creating handler classes, see "Creating Namespace Handlers".

The handler class must be able to process the associated XML elements and attributes as they occur within the cache configuration file. More specifically, during the processing of the XML DOM for the cache configuration, all XML elements and attributes that occur within a namespace are passed to the associated handler instance for processing. The following example uses the MyNamespaceHandler to process all elements that use the ex namespace prefix.

<?xml version="1.0"?>

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
   xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config
      coherence-cache-config.xsd"
   xmlns:ex="class://MyNamespaceHandler">

   <ex:myelement/>
   ...

Guidelines for Declaring an XML Namespace

Use the following guidelines when declaring an XML namespace:

  • A URI class must implement the NamespaceHandler interface; otherwise, the processing of the cache configuration file fails.

  • XML elements and attributes must not use an undeclared namespace prefix; otherwise, the processing of the cache configuration file fails.

  • The default handler that is used for Coherence elements and attributes cannot be overridden; otherwise, the processing of the cache configuration file fails. The default namespace is reserved for Coherence.

Creating Namespace Handlers

Namespace handlers are used to process XML elements and attributes that belong to a specific XML namespace. Each unique namespace that is used in a cache configuration file requires a namespace handler. Namespace handlers must implement the NamespaceHandler interface. Typically, namespace handlers extend the base AbstractNamespaceHandler implementation class, which provides convenience methods that can simplify the processing of complex namespaces. Both of these APIs are discussed in this section and are included in the com.tangosol.config.xml package. For details about the APIs, see Java API Reference for Oracle Coherence.

This section includes the following topics:

Implementing the Namespace Handler Interface

Namespace handlers process the elements and attributes that are used within an XML namespace. Namespace handlers can directly implement the NamespaceHandler interface. The interface relies on the DocumentPreprocessor, ElementProcessor, and AttributeProcessor interfaces. XML processing is performed within a processing context as defined by the ProcessingContext interface.

Elements and attributes that are encountered in a namespace must be processed by a processor implementation. Element and attribute processors are responsible for processing, parsing, and type conversion logic. Document preprocessors are used to mutate elements, if required, before they are processed.

Example 18-1 provides a basic NamespaceHandler implementation. The GreetingNamespaceHandler implementation processes a <message> element using an ElementProcessor implementation (MessageProcessor), which is included as an inner class. For the example, the following XML is assumed:

<?xml version="1.0"><cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config" 
   xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config 
      coherence-cache-config.xsd"
   xmlns:ex="class://com.examples.GreetingNamespaceHandler">

   <ex:message>hello</ex:message>
   ...

Example 18-1 Handler Implementation Using the NamespaceHandler Interface

import com.tangosol.config.ConfigurationException;
import com.tangosol.config.xml.AttributeProcessor;
import com.tangosol.config.xml.DocumentPreprocessor;
import com.tangosol.config.xml.ElementProcessor;
import com.tangosol.config.xml.NamespaceHandler;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.run.xml.XmlAttribute;
import com.tangosol.run.xml.XmlElement;
import java.net.URI;

public class GreetingNamespaceHandler implements NamespaceHandler
{
   public AttributeProcessor getAttributeProcessor(XmlAttribute xmlAttribute) 
   {
      return null;
   }
 
   public DocumentPreprocessor getDocumentPreprocessor()
   {
      return null;
   }
 
   public ElementProcessor getElementProcessor(XmlElement xmlElement)
   {
      if (xmlElement.getName().equals("ex:message"))
      {
         MessageProcessor  mp = new MessageProcessor();
         return mp;
      }
      else
      {
         throw new RuntimeException("Unknown element type " +
            xmlElement.getQualifiedName());
      }
   }

   public void onEndNamespace(ProcessingContext processingContext, 
      XmlElement xmlElement, String string, URI uRI) 
   {
   }
 
   public void onStartNamespace(ProcessingContext processingContext, 
      XmlElement xmlElement, String string, URI uRI) 
   {
   }

   public class MessageProcessor implements ElementProcessor
   {
      public Object process(ProcessingContext processingContext, 
         XmlElement xmlElement) throws ConfigurationException 
      {
         System.out.println("Greeting is: " + xmlElement.getString());
         return null;
      }
   }
}

The above class handles the <ex:message> element content. However, it does not distinguish between the different types of elements that may occur. All elements that occur within the default XML namespace are provided to the same process method. In order to process each type of XML element, conditional statement are required within the process method. This technique may be sufficient for trivial XML content; however, more complex XML content may require many conditional statements and can become overly complicated. A more declarative approach is provided with the AbstractNamespaceHandler class.

Namespace Handler Callback Methods

The NamespaceHandler interface provides the onStartNamespace and the onEndNamespace callback methods. These methods allow additional processing to be performed on the first and last encounter of a namespace in a cache configuration file.

Extending the Namespace Handler Abstract Class

The AbstractNamespaceHandler class provides a useful and extensible base implementation of the NamespaceHandler, ElementProcessor and AttributeProcessor interfaces together with mechanisms to register processor for specifically named elements and attributes. The class simplifies the processing of elements and attributes and can, in most cases, remove the requirement to directly implement the element and attribute processor interfaces.

This section contains the following topics:

Registering Processors

The AbstractNamespaceHandler class provides methods for declaratively registering element and attribute processors. The methods alleviate the need to check element names and types. There are two registration mechanisms: explicit registration and implicit registration.

Explicit Processor Registration

To use explicit processor registration, call the registerProcessor method within a sub-class constructor and manually register both element and attribute processors. Example 18-2 re-implements Example 18-1 and uses the registerProcessor method to register the MessageProcessor element processor.

Example 18-2 AbstractNamespaceHandler Implementation with Explicit Registration

import com.tangosol.config.ConfigurationException;
import com.tangosol.config.xml.AbstractNamespaceHandler;
import com.tangosol.config.xml.ElementProcessor;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.run.xml.XmlElement;

public class GreetingNamespaceHandler extends AbstractNamespaceHandler 
{
   public GreetingNamespaceHandler()
   {
      registerProcessor("message", new MessageProcessor());
   }

   public class MessageProcessor implements ElementProcessor
   {
      public Object process(ProcessingContext processingContext, 
         XmlElement xmlElement) throws ConfigurationException 
      {
         System.out.println("Greeting is: " + xmlElement.getString());
         return null;
      }
   }
}

Implicit Processor Registration

To use implicit processor registration, annotate processor classes with the @XmlSimpleName annotation. The processors are automatically registered for use within the associated namespace.The @XmlSimpleName annotation is used to determine which of the processor implementations are appropriate to handle XML content encountered during XML DOM processing. If an XML element or attribute is encountered for which there is no defined processor, then the onUnknownElement or onUnknownAttribute methods are called, respectively. The methods allow corrective action to be taken if possible. By default, a ConfigurationException exception is raised if unknown XML content is discovered during processing.

Example 18-3 re-implements Example 18-1 and uses the @XmlSimpleName annotation to register the MessageProcessor element processor.

Example 18-3 AbstractNamespaceHandler Implementation with Implicit Registration

import com.tangosol.config.ConfigurationException;
import com.tangosol.config.xml.AbstractNamespaceHandler;
import com.tangosol.config.xml.ElementProcessor;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.config.xml.XmlSimpleName;
import com.tangosol.run.xml.XmlElement;

public class GreetingNamespaceHandler extends AbstractNamespaceHandler 
{
   public GreetingNamespaceHandler()
   {
   }

   @XmlSimpleName("message")
   public class MessageProcessor implements ElementProcessor
   {
      public Object process(ProcessingContext processingContext, 
         XmlElement xmlElement) throws ConfigurationException 
      {
         System.out.println("Greeting is: " + xmlElement.getString());
         return null;
      }
   }
}

Using Injection to Process Element Content

Element and attribute processors are used to hand-code the processing of XML content. However, the task can be repetitive for complex namespaces. To automate the task, the ProcessingContext.inject method is capable of injecting strongly typed values into a provided object, based on identifiable setter methods and values available for a specified XML element.

For example, given the following XML:

<ex:message>
   <ex:english>hello</ex:english>
</ex:message>

An element processor can be used to hand-code the processing of the <english> element:

import com.tangosol.config.ConfigurationException;
import com.tangosol.config.xml.AbstractNamespaceHandler;
import com.tangosol.config.xml.ElementProcessor;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.config.xml.XmlSimpleName;
import com.tangosol.run.xml.XmlElement;

public class GreetingNamespaceHandler extends AbstractNamespaceHandler 
{
   public GreetingNamespaceHandler()
   {
   }

   @XmlSimpleName("message")
   public class MessageProcessor implements ElementProcessor
   {
      public Object process(ProcessingContext processingContext, 
         XmlElement xmlElement) throws ConfigurationException 
      {
         String engMsg = processingContext.getMandatoryProperty("english",
            String.class, xmlElement);
         Message message = new Message();
         message.setEnglish(engMsg);
         System.out.println("Greeting is: "+ message.getEnglish());

         return message;
      }
   }
}

As an alternative, the inject method can perform the processing:

@XmlSimpleName("message")
public class MessageProcessor implements ElementProcessor
{
   public Object process(ProcessingContext processingContext, 
      XmlElement xmlElement) throws ConfigurationException 
   {
      return processingContext.inject(new Message(), xmlElement);
   }
}

The inject method uses Java reflection, under the assumption that the object to be configured follows the Java bean naming conventions. First the inject method identifies the appropriate setter methods that may be called on the Java bean. Typically, this is achieved by locating setter methods that are annotated with the @Injectable annotation. Next, it determines the appropriate types of values required by the setter methods. Lastly, it uses the provided XmlElement to locate, parse, convert, coerce, and then set appropriately typed values into the object using the available setter methods. The inject method supports primitives, enumerations, formatted values, complex user-defined types, and collection types (sets, lists, and maps). A ConfigurationException exception is thrown if the inject method fails. For example, it fails to format a value into the expected type.

The following example demonstrates a Message class for the above example that supports injection with the inject method:

import com.tangosol.config.annotation.Injectable;

public class Message
{
 
   private String m_sEnglish = "a greeting";
   public Message()
   {
   }

   @Injectable("english")
   public void setEnglish (String sEnglish)
   {
      m_sEnglish = sEnglish;
   }
   
   public String getEnglish()
   {
      return m_sEnglish;
   }
}

Note:

If the @Injectable annotation property is omitted, then the inject method tries to use the setter method Java bean name. For the above example, @injectable("") results in the use of english.

Typically, element and attribute processors follow the same pattern when using the inject method. For example:

@XmlSimpleName("element")
public class XProcessor implements ElementProcessor
{
   public Object process(ProcessingContext processingContext, 
      XmlElement xmlElement) throws ConfigurationException 
   {
      return processingContext.inject(new X(), xmlElement);
   }
}

Declarations and registrations for such implementations can be automated using the registerElementType and registerAttributeType methods for elements and attributes, respectively. These methods are available in the AbstractNamespaceHandler class and are often used in constructors of AbstractNamespaceHandler sub-classes. The following example demonstrates using the registerElementType method and does not require a processor implementation.

Note:

To support type-based registration of classes, the specified class must provide a no-argument constructor.

import com.tangosol.config.xml.AbstractNamespaceHandler;

public class GreetingNamespaceHandler extends AbstractNamespaceHandler
{
    
   public GreetingNamespaceHandler()
   {   
   registerElementType("message", Message.class);
   }
}

Example: the JNDI Resource Namespace Handler

The JNDI resource namespace handler is reproduced here from the Coherence Incubator project that is hosted on java.net (http://java.net/projects/cohinc). The Incubator contains several namespace handler implementations, which can be found within the Common Package module.

The JNDI resource namespace handler provides the ability to lookup and reference resources defined by a JNDI context. The use of the namespace handler is often used to replace the need to statically create resources using the <class-scheme> or <instance> elements in the cache configuration file.

This section includes the following topics:

Create the JNDI Resource Namespace Handler

The JNDI resource namespace handler is used at runtime to process <resource> elements that are found in a cache configuration file. The handler extends the AbstractNamespaceHandler class and registers the JndiBasedParameterizedBuilder class for the <resource> element. The following example shows the namespace handler definition.

import com.tangosol.coherence.config.builder.ParameterizedBuilder;
import com.tangosol.config.xml.AbstractNamespaceHandler;

public class JndiNamespaceHandler extends AbstractNamespaceHandler
{
    public JndiNamespaceHandler()
    {
        registerElementType("resource", JndiBasedParameterizedBuilder.class);
    }
}

The JndiBasedParameterizedBuilder class performs a JNDI context lookup to locate and create an object using the name and initialization parameters that are provided in the <resource-name> and <init-parms> elements, respectively. The setter methods for these elements (setResourceNameExpression and setParameterList) use the @Injectable annotation to pass the values configured in the cache configuration files.

import com.tangosol.coherence.config.ParameterList;
import com.tangosol.coherence.config.SimpleParameterList;
import com.tangosol.coherence.config.builder.ParameterizedBuilder;
import com.tangosol.coherence.config.builder.ParameterizedBuilder.
   ReflectionSupport;
import com.tangosol.config.annotation.Injectable;
import com.tangosol.config.expression.Expression;
import com.tangosol.config.expression.LiteralExpression;
import com.tangosol.config.expression.Parameter;
import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.util.Base;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JndiBasedParameterizedBuilder implements
   ParameterizedBuilder<Object>, ReflectionSupport
{   private static final Logger logger =
      Logger.getLogger(JndiBasedParameterizedBuilder.class.getName());

   private Expression<String> m_exprResourceName;

   private ParameterList m_parameterList;

   public JndiBasedParameterizedBuilder()
   {
      m_exprResourceName = new LiteralExpression<String>("");
      m_parameterList = new SimpleParameterList();
   }
 
   public Expression<String> getResourceNameExpression()
   {
      return m_exprResourceName;
   }
 
   @Injectable("resource-name")
   public void setResourceNameExpression(Expression<String> exprResourceName)
   {
      m_exprResourceName = exprResourceName;
   }
 
   public ParameterList getParameterList()
   {
      return m_parameterList;
   }
 
   @Injectable("init-params")
   public void setParameterList(ParameterList parameterList)
   {
      m_parameterList = parameterList;
   }
 
   public boolean realizes(Class<?> clazz,
                           ParameterResolver parameterResolver,
                           ClassLoader classLoader)
   {
      return clazz.isAssignableFrom(realize(parameterResolver, classLoader,
         null).getClass());
    }
 
   public Object realize(ParameterResolver parameterResolver,
                         ClassLoader classLoader,
                         ParameterList parameterList)
   {
      InitialContext initialContext;
       try
      {
         String sResourceName = m_exprResourceName.evaluate(parameterResolver);
         Hashtable<String, Object> env = new Hashtable<String, Object>();

         for (Parameter parameter : m_parameterList)
         {
            env.put(parameter.getName(), parameter.evaluate(parameterResolver));
         }

         initialContext = new InitialContext(env);

         if (logger.isLoggable(Level.FINE))
         {
            logger.log(Level.FINE,
                       "Looking up {0} using JNDI with the environment {1}",
                        new Object[] {sResourceName, env});
         }

         Object resource = initialContext.lookup(sResourceName);

         if (logger.isLoggable(Level.FINE))
         {
            logger.log(Level.FINE, "Found {0} using JNDI", resource);
         }
 
         return resource;
      }
      catch (NamingException e)
      {
         throw Base.ensureRuntimeException(e, "Unable to resolve the JNDI
            resource: " + m_exprResourceName.toString());
      }
   }
 
   public String toString()
   {
      return String.format("%s{resourceName=%s, parameters=%s}",
                           this.getClass().getName(), m_exprResourceName,
                           m_parameterList);
   }
}

Declare the JNDI Namespace Handler

The JNDI resource namespace handler must be declared within the cache configuration file. Declare the namespace by providing the URI to the handler class and assigning a namespace prefix. Any prefix can be used. The following example uses jndi as the prefix:

<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/
                 coherence-cache-config coherence-cache-config.xsd"
              xmlns:jndi="class://com.examples.JndiNamespaceHandler">
...

Use the JNDI Resource Namespace Handler

The JNDI resource namespace handler can be used whenever an application requires a resource to be located and created. In addition, the namespace can be used when defining custom implementations using the <class-scheme> or <instance> elements. Based on the handler implementation, the <resource> and <resource-name> element are required and the <init-params> element is optional.

The following example uses a JNDI resource for a cache store when defining a distributed scheme:

<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/
                 coherence-cache-config coherence-cache-config.xsd"
              xmlns:jndi="class://com.examples.JndiNamespaceHandler">
...

   <caching-schemes>
      <distributed-scheme>
         <scheme-name>distributed-rwbm</scheme-name>
         <backing-map-scheme>
            <read-write-backing-map-scheme>
               <internal-cache-scheme>
                  <local-scheme/>
               </internal-cache-scheme>
               <cachestore-scheme>
                  <class-scheme>
                     <jndi:resource>
                        <jndi:resource-name>MyCacheStore</jndi:resource-name>
                        <init-params>
                           <init-param>
                              <param-type>java.lang.String</param-type>
                              <param-value>{cache-name}</param-value>
                           </init-param>
                        </init-params>
                     </jndi:resource>
                  </class-scheme>
               </cachestore-scheme>
            </read-write-backing-map-scheme>
         </backing-map-scheme>
      </distributed-scheme>
   <caching-schemes>
</cache-config>

The following example uses a JNDI resource to resolve a DNS record:

<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/
                 coherence-cache-config coherence-cache-config.xsd"
              xmlns:jndi="class://com.examples.JndiNamespaceHandler">
...
<jndi:resource>
   <jndi:resource-name>dns:///www.oracle.com</jndi:resource-name>
</jndi:resource>

The following example uses a JNDI resource to resolve a JMS connection factory:

<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/
                 coherence-cache-config coherence-cache-config.xsd"
              xmlns:jndi="class://com.examples.JndiNamespaceHandler">
...

<jndi:resource>
   <jndi:resource-name>ConnectionFactory</jndi:resource-name>
   <init-params>
      <init-param>
         <param-name>java.naming.factory.initial</param-name>
         <param-value>org.apache.activemq.jndi.ActiveMQInitialContextFactory
         </param-value>
      </init-param>
      <init-param>
         <param-name>java.naming.provider.url</param-name>
         <param-value system-property="java.naming.provider.url"></param-value>
      </init-param>
   </init-params>
</jndi:resource>