Since version 0.5, Aspire moved from using W3C DOM XML as the native method for encoding metadata, and has moved over to a JSON compatible structure called "AspireObject".
Prior to version 0.5, Aspire was always proudly "XML throughout" and all metadata was always been specified in XML DOM objects.
However, over time, some weaknesses of this approach have arisen:
All of these weaknesses are resolved with the new AspireObject container.
To maintain performance and low memory usage, AspireObject is not thread safe. It is expected that AspireObject is only used by a single thread at a time. This is the same as W3C XML DOM, which was also not thread safe.
AspireObject brings substantive benefits for Aspire overall:
This change, however, was not without its challenges:
Finally, as we have gone through this process, we are happy to report that the overall design of Aspire is cleaner and more programmer-friendly throughout. Some improvements enabled by this change include:
Note that Aspire configuration files are still represented in XML. There is no plan to change this at any time in the future.
In order to make Aspire more friendly for JavaScript-based user interfaces (such as the new Admin console), all component status is now represented in AspireObject objects can can be reported by Aspire in either XML or JSON.
The new metadata holder is called an "object" because it is equivalent to a JSON object, with extensions that make it more compatible with XML.
JSON support in AspireObject now includes:
Maps: All AspireObject instances can have a nested Map of name/value pairs.
Primitive Types: Values stored in AspireObject's can be primitive types such as Integer, Float, Boolean, etc., not just String.
Lists: AspireObject values can hold lists of objects. Items in the list can be any type, from a primitive object to a full embedded map.
Created from JSON: AspireObjects can be created from strings and streams of characters which represent JSON content.
Write to JSON: AspireObjects can be written out as valid JSON.
The AspireObject has extensions to simple JSON objects specifically to improve handling of XML. This is critically important because XML is used so heavily in and throughout Aspire document processing.
Therefore, several extensions to JSON were added to create an AspireObject which works well with XML:
AspireObject's are named - Every AspireObject has a name. This makes AspireObject's roughly equivalent to XML elements.
Attribute Handling - Special methods exist on AspireObject for setting and getting attributes. Attributes can be added to any object (i.e. Strings, Integers, etc.) which are stored in AspireObject's.
Repeated Elements - Repeated elements are automatically stored as embedded arrays in AspireObject when read from XML. These are automatically unwound when writing the object back to XML.
Mixed Content Handling - Elements which contain mixed text and embedded markup are automatically stored as arrays of content.
XML can be read directly into AspireObject's - AspireObject can be built from SAX streams, allowing XML to automatically build an AspireObject.
AspireObjects can be written to XML - AspireObject's can be used as an XMLReader which produces a SAX event stream. This can be serialized to files or transformed into W3C DOM as necessary.
AspireObjects can be transformed - Since AspireObjects can produce SAX streams, they can also be inputs to XSLT transformations.
AspireObjects can be searched with XPath - Apache JXPath has been implemented as a method for searching AspireObject trees, so that standard XPath expressions can be used to access data from AspireObjects.
Creates an empty AspireObject.
AspireObject aspireObj = new AspireObject("doc"); |
JSON:
{"doc": null} |
XML:
<doc/> |
aspireObj.add("title", "Call of the Wild"); aspireObj.add("author", "Jack London"); |
JSON:
{ "doc": { "title": "Call of the Wild", "author": "Jack London", } } |
XML:
<doc> <title>Call of the Wild</title> <author>Jack London</author> </doc> |
Using set() to Change a Value
aspireObj.set("title", "White Fang"); |
JSON:
{ "doc": { "title": "White Fang", "author": "Jack London", } } |
XML:
<doc> <title>White Fang</title> <author>Jack London</author> </doc> |
AspireObjects Have Names
Like XML Elements, AspireObjects have names:
AspireObject aspireObj = new AspireObject("doc", "this is the content of my doc");
These named objects are represented in JSON as a parent-map:
{"doc": "this is the content of my doc"} |
In XML:
<doc>this is the content of my doc</doc> |
In the above example, "doc" is not actually a key for any map. It is merely the name of the AspireObject.
All children added to an Aspire object will also have names:
AspireObject aspireObj = new AspireObject("doc"); aspireObj.add("title","A supercalifragilistic title!"); aspireObj.add("author","Dick Van Dyke"); |
These are stored in the aspireObj in a map and are represented in JSON as a nested JSON object:
{ "doc": { "title": "A supercalifragilistic title!", "author": "Dick Van Dyke" } } |
And in XML:
<doc> <title>A supercalifragilistic title!</title> <author>Dick Van Dyke</author> </doc> |
...even those which are just simple Strings or Integers.
When adding named elements to an AspireObject using add(), new AspireObject's are created for each element added:
AspireObject aspireObj = new AspireObject("doc"); AspireObject titleObj = aspireObj.add("title","Call of the Wild"); |
titleObj instanceof AspireObject
This small amount of extra overhead allows for these objects to contain attributes, an important feature of XML.
In the above example, titleObj is an AspireObject which contains a String, and not String itself.
In addition, child elements are all named:
titleObj.getName() --> Returns "title" titleObj.getContent() --> Returns "Call of the Wild
The following XML structure:
<doc> <file>a.txt</file> <file>b.txt</file> <file>c.txt</file> </doc>
Will be represented in JSON as an array:
{ "doc": { "file": ["a.txt", "b.txt", "c.txt"] } } |
This structure will be implemented automatically using the add() method of AspireObject:
AspireObject aspireObj = new AspireObject("doc"); aspireObj.add("file", "a.txt"); aspireObj.add("file", "b.txt"); aspireObj.add("file", "c.txt"); |
Note that get("file") will return just the first child:
aspireObj.get("file") --> Returns the AspireObject which contains "a.txt"
Therefore, get() will always return an AspireObject. The value can be null if the child can not be found.
A special method exists to fetch all children for a node:
List<AspireObject> allFiles = aspireObj.getAll("file"); // Always returns a list, even with just one allFiles.size() --> Is "3"
getAll() will always return a List<AspireObject> which is never null. If there are no children, it will return an empty list.
Because all AspireObjects are named, this makes fetching children quite useful:
AspireObject aspireObj = new AspireObject("doc"); aspireObj.add("title", "A clutch of files"); aspireObj.add("file", "a.txt"); aspireObj.add("file", "b.txt"); aspireObj.add("author", "Rip Van Winkle"); List<AspireObject> childrenList = aspireObj.getChildren(); childrenList.size() --> return 4 childrenList.get(2).getName() --> returns "file" childrenList.get(2).getContent() --> returns "b.txt" |
In the above example, childrenList contains all children, including both instances of the "file" child.
AspireObjects can have both content as well as name/value pairs. This is an extension over standard JSON objects to provide better XML support.
aspireObj.setContent("This is the content of my node"); aspireObj.getContent() // Returns "This is the content of my node" aspireObj.addContent("More Content") // Adds more content to the existing content (creates a list of content objects) |
AspireObject aspireObj = new AspireObject("doc"); aspireObject.add("name", "George Washington"); |
JSON:
{"doc": {"name": "George Washington" <<< "name" is actually an embedded AspireObject, but printed here as a simple string } } |
XML:
<doc> <name>George Washington</name> </doc> |
AspireObject aspireObj = new AspireObject("doc"); AspireObject nameObj = aspireObject.add("name", "George Washington"); nameObj.setAttribute("jobType", "president"); |
JSON:
{"doc": {"name": {"$": "George Washington", "@jobType": "president"} } } |
XML:
<doc> <name jobType="president">George Washington</name> </doc> |
...to handle embedded markup
XML content can have text and embedded tags mixed. When this occurs, the content will be represented in the AspireObject as a list of items. This can be implemented with addContent(), as follows:
AspireObject aspireObj = new AspireObject("doc"); AspireObject title = aspireObj.add("title"); title.addContent("This is a "); title.addContent(new AspireObject("b,"really big")); title.addContent(" statement of truth!"); |
When written to JSON, this will look like this:
{ "doc": { "title": { "$": ["This is a ", {"b:"really big"}, " statement of truth!"] } } } |
Note that when the content is a list, it must be put under the "$" tag, to distinguish it from situations where there are multiple XML tags with the same name.
In XML it looks like this:
<doc> <title>This is a <b>really big</b> statement of truth!</title> </doc> |
Now suppose the document has attributes:
title.setAttribute("type", "lovely");
The JSON will now look like this:
{ "doc": { "title": { "$": ["This is a ", {"b:"really big"}, " statement of truth!"] "@type": "lovely" } } } |
In XML:
<doc> <title type="lovely">This is a really big statement of truth!</title> </doc> |
AspireObjects have special built-in support for attributes to make them more compatible with XML.
aspireObj.setAttribute("type", "human"); // Set the type attribute on the object String type = aspireObj.getAttribute("type"); // Fetches the type attribute from the AspireObject |
Attribute values can only be string
aspireObj.setAttribute("type", 32) // Throws an exception aspireObj.setAttribute("type", new AspireObject("data")) // Throws an exception |
AspireObject aspireObj = new AspireObject("doc"); aspireObj.setAttribute("from", "US-GPO"); AspireObject nameObj = aspireObj.add("name", "Obama"); nameObj.setAttribute("type", "president"); |
JSON:
{ "doc": { "@from": "US-GPO", "name": { "$": "Obama", "@type": "president" } } } |
XML:
<doc from="US-GPO"> <name type="president">Obama</name> </doc> |
It is possible to create an object with null as its content and with no named elements:
AspireObject aspireObj = new AspireObject("doc");
In JSON this is represented as:
{"doc": null}
In XML this is represented as:
<doc/>
Adding named elements with no content
New named elements added to an AspireObject with no content will be assumed to be null:
AspireObject aspireObj = new AspireObject("doc"); aspireObj.add("title"); |
In JSON this is represented as:
{ "doc": { "title":null } } |
In XML this is represented as:
<doc> <title/> </doc> |
Adding null content is generally ignored
The following are ignored (do not throw an exception):
AspireObject aspireObj = new AspireObject("doc"); aspireObj.addContent(null); // ignored aspireObj.add("$"); // ignored, same as addContent(null) |