Writing a SPIN Query
A SPIN network is a collection of nodes; objects that can perform queries. SPIN nodes join together to form networks that allow queries to be broadcast from node to node and the results are then aggregated in the reverse order.
Nodes can be accessed directly by code running in the same JVM, or exposed over HTTP via an application server (Clint – do you mean app server or servelet container).
This document explains how to write the SPIN plug-in used to implement a query, and it provides client code that can be used to perform this query remotely.
Resources
- SPIN sources: https://scm.chip.org/svn/repos/spin/base/trunk/
- Example SPIN extension in Java and Scala: https://scm.chip.org/svn/repos/spin/base/trunk/examples
System Requirements
- Sun/Oracle Java Development Kit (JDK) 1.6.0_04 or later. Other JDKs, including OpenJDK, are not supported but will likely work.
- Apache Maven 2.2.1. Maven 3.x is not supported, but will likely work. (Required to build SPIIN and the example module)
Hello, Spin
* *The queries that SPIN nodes perform are implemented as instances of the QueryAction interface.
public interface QueryAction<Criteria> { String perform(final QueryContext context, final Criteria criteria) throws QueryException; Criteria unmarshal(final String serializedCriteria) throws SerializationException; boolean isReady(); void destroy(); }
QueryActions takes a serialized input criteria and returns serialized results. It is up to the implementer to choose a serialization format, if any.
Queries are submitted to a SPIN network using either the Agent or Querier class. The Querier class is higher level, Itis used in this document and the examples module.
Implement QueryAction
A simple QueryAction is one that echoes its input. Here's how it would look in Scala:
final class EchoQueryAction extends QueryAction[String] { override def unmarshal(serialized: String) = serialized override def perform(context: QueryContext, input: String) = input override def isReady = true override def destroy { } }
This could be simplified by extending AbstractQueryAction, which provides default implementations for isReady() and destroy() which are suitable for a stateless QueryAction like EchoQueryAction:
final class EchoQueryAction extends AbstractQueryAction[String] { override def unmarshal(serialized: String) = serialized override def perform(context: QueryContext, input: String) = input }
A QueryAction's unmarshal method defines how the input criteria is unmarshalled into a Java object. In this case, we just echoing our input, so we don't need to deserialize.
Consider a slightly more complicated QueryAction, that receives as input a list of integers and returns the sum. Here the serialization format is XML, so we can extends JAXBQueryAction, which supplies an implementation of unmarshal() that uses JAXB to turn raw XML into a object in the JVM:
final class AddQueryAction extends JAXBQueryAction(classOf[AddInput]) { //Take an AddInput, and return an XML-serialized AddResult override def perform(context: QueryContext, input: AddInput): String = { val result = new AddResult(input.toAdd.sum) JAXBUtils.marshalToString(result) } }
This class takes input like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:AddInput xmlns:ns2="http://spin.org/xml/res/">
<number xsi:type="xs:int" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">1</number>
<number xsi:type="xs:int" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2</number>
<number xsi:type="xs:int" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3</number>
</ns2:AddInput>
which gets unmarshalled into a class like
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AddInput", namespace = "http://spin.org/xml/res/")
@XmlRootElement(name = "AddInput", namespace = "http://spin.org/xml/res/")
//The only field is a Java List, which works with JAXB
final case class AddInput(private val number: JList[Int])
by JAXB.
Returning Results
QueryActions return results as serialized Strings. Spin is agnostic about the contents of the returned String, and, as with input, the serialization format used.
In this case, if we define a class that's serializable by JAXB, like:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AddResult", namespace = "http://spin.org/xml/res/")
@XmlRootElement(name = "AddResult", namespace = "http://spin.org/xml/res/")
final case class AddResult(val sum: Int)
We can use Spin's JAXBUtils class to easily serialize an instance into a String:
final class AddQueryAction extends JAXBQueryAction(classOf[AddInput]) {
//Take an AddInput, and return an XML-serialized AddResult
override def perform(context: QueryContext, input: AddInput): String = {
val result = new AddResult(input.toAdd.sum)
JAXBUtils.marshalToString(result)
}
}