Global Context Enhancements
The current GC is based upon a hierarchical data store, meaning a request with a GC key requires a "walk" through a hierarchy of
SubContexts down to the specified leaf node (value).
For M2, we'll move to a "flat GC", meaning no more hierarchical storage. Rather, a GC zone can be thought of as a Map. This change simplifies the concepts and improves performance.
Porting notes
- GC support in trunk is functionally complete. Problems should be reported as bugs.
- The
/user zone uses in-memory persistence for now.
- All GC URIs must start with a slash (/request/method is valid; request/method is not).
- GC URIs have changed. *The old GC supported transparent value pathing, meaning
/app/foo/bar might refer to any of the following:
-
- A value stored at
/app/foo/bar
- The value at
bar in a Map stored at /app/foo
- The new GC is explicit about value pathing.
/app/foo/bar refers to a valued stored at that key. /app/foo#bar refers to the value at bar in a Map stored at /app/foo.
- Config files
- Syntax is unchanged.
- "config" data is now stored in the
/config zone. For example, [/app/handlers] should be changed to [/config/handlers].
- Maps are now flattened when placed into the new GC. For example, the following used to result in a Map stored at
/app/foo:
[/app/foo]
bar = x
Now, that results in the value
x stored at
/app/foo/bar.
- No more
SubContext. Check out list() as an alternative. For example, replace the following based on the old GC:
SubContext headers = GlobalContext.condGet(Request.Headers.out, null);
if (headers != null) {
for (String header : headers.keys()) {
Object header_value = GlobalContext.get(headers, header);
with
List<String> headerPaths = GlobalContext.list(Request.Headers.out);
for (String headerPath : headerPaths) {
String header_name = headerPath.substring(headerPath.lastIndexOf('/'));
Object header_value = GlobalContext.get(headerPath);
- GC contents can be viewed with
String GlobalContext.dump(String uri).
- The GlobalContext viewer (Eclipse) doesn't yet work with the new GC. In the meantime, you can setup an "expression" in the debugger to invoke the
dump() API.
Key elements
The key elements include the GC APIs and extensible zone and type handlers. The GC APIs delegate storage to the appropriate zone handler; value manipulation is delegated to the appropriate type handler.
GC path syntax
GC path syntax:
/<zone>/<key>
GC APIs
| GlobalContext APIs | Description |
V get(String path) | Returns the value stored at path (null is a valid value), if found; throws an IllegalArgumentException otherwise. |
V condGet(String path, V default) | Returns the value stored at path (null is a valid value), if found; returns default otherwise. |
boolean containsKey(String path) | Returns true if the path is found; false otherwise. |
List<String> list(String prefix) | Returns a list of defined paths that start with the specified pathPrefix. |
List<String> list(String prefix, boolean includePrefix) | Returns a list of paths that start with the specified pathPrefix. If includePrefix == false, then the paths are relative to the prefix (e.g. list("/request", true) would return paths like /request/foo/bar; list("/request", false) would return foo/bar ). |
T put(String path, V o) | Replaces a value, if present; else creates an entry. Returns previous value associated with the path, or null if the path was not found. Throws an IllegalArgumentException if no zone handler is found. |
T post(String path, V o) | Appends o to the value, if present; else creates an entry. Returns previous value associated with the path, or null if the path was not found. Support for "append" is a function of the zone handler. Throws an IllegalArgumentException if no zone handler is found. |
void delete(String path) | Removes the entry. Equivalent to delete(path, false). |
void delete(String path, boolean deleteChildren) | If deleteChildren == true, then removes all entries whose path starts with path; otherwise, removes just the entry at path. |
String dump(String uri) | Returns a formatted String listing the GC content at and below the specified uri. Throws an IllegalArgumentException if no zone handler is found. |
Value manipulation
"Value manipulation" refers to the ability to specify sub-elements within a value by appending a fragment identifier to the path. Details about the fragment (syntax and meaning) are dependent on the type handler associated with the value.
In general,
put and
post support emulates that of the corresponding HTTP methods:
-
put() means create (if not defined) or replace (otherwise)
-
post() means create (if not defined) or append (otherwise)
Maps
| Java API | Groovy API | Description | Can create entry |
GlobalContext.put("/app/myMap", map) | app.myMap = map | Create/replace existing map | |
GlobalContext.put("/app/myMap#foo", "bar") | app.myMap['foo'] = 'bar' | Add/replace entry; map must exist | |
| | | | |
GlobalContext.post("/app/myMap", map) | app.myMap << map | Create/merge into existing map; replaces existing keys | |
| | | | |
GlobalContext.get("/app/myMap") | x = app.myMap.get() | Returns full map | |
GlobalContext.get("/app/myMap#foo") | x = app.myMap['foo'] | Returns entry at key foo | |
| | | | |
GlobalContext.delete("/app/myMap") | _gc.delete('/app/myMap') | Deletes map | |
GlobalContext.delete("/app/myMap#foo") | _gc.delete('/app/myMap#foo') | Removes entry | |
Note that
GlobalContext.post("/app/myMap#foo", "bar") is not valid.
Fragments supported by the
Map type handler:
Lists
| Java API | Groovy API | Description | Can create entry |
GlobalContext.put("/app/myList", list) | app.myList = list | Create/replace existing list | |
GlobalContext.put("/app/myList#0", "bar") | app.myList[0] | Set/replace list element; list must exist | |
| | | | |
GlobalContext.post("/app/myList", list) | app.myList << list | Create/append to list | |
GlobalContext.post("/app/myList", "bar") | app.myList << 'bar' | Append to list; list must exist | |
| | | | |
GlobalContext.get("/app/myList") | x = app.myList.get() | Returns full list | |
GlobalContext.get("/app/myList#0") | x = app.myList[0] | Returns 0th element | |
| | | | |
GlobalContext.delete("/app/myList") | _gc.delete("/app/myList") | Deletes list | |
GlobalContext.delete("/app/myList#0") | _gc.delete("/app/myList[0]") | Removes first entry | |
Fragments supported by the
List type handler:
FirstElementList
FirstElementList is a variant of List that supports access to the first element as a simple object (without needing to specify the index). This support was added as a means to simplify handling of values like query parameters, which are properly stored as lists but are most commonly retrieved as single values.
Within the default zones, only
/request/params/<parmName> and
/request/headers/in/<headerName> are instances of
FirstElementList.
| Java API | Groovy API | Description | Can create entry |
GlobalContext.put("/app/myList", list) | app.myList = list | Create list; list does not exist | |
GlobalContext.put("/app/myList#*", list) | app.myList['*'] = list | Replace list; list must exist | |
GlobalContext.put("/app/myList", "bar") | app.myList = "bar" | Set/replace first element; list must exist | |
GlobalContext.put("/app/myList#0", "bar") | app.myList[0] | Set/replace list element; list must exist | |
| | | | |
GlobalContext.post("/app/myList", list) | app.myList << list | Create list; list does not exist | |
GlobalContext.post("/app/myList#*", "bar") | app.myList['*'] << "bar" | Append to list; list must exist | |
| | | | |
GlobalContext.get("/app/myList") | x = app.myList.get() | Returns first element | |
GlobalContext.get("/app/myList#0") | x = app.myList[0] | Returns first element | |
GlobalContext.get("/app/myList#*") | x = app.myList['*'] | Returns full list | |
| | | | |
GlobalContext.delete("/app/myList#*") | _gc.delete("/app/myList#*") | Deletes list | |
GlobalContext.delete("/app/myList#0") | _gc.delete("/app/myList[0]") | Removes first entry | |
Fragments supported by the
List type handler:
Samples
app.aList = new FirstElementArrayList<String>()
assert app.aList == null
// app.aList[0] would throw an IllegalArgumentException
Objects (default)
| Java API | Groovy API | Description | Can create entry |
GlobalContext.put("/app/myObj", o) | app.myObj = o | Create/replace object | |
| | | | |
GlobalContext.get("/app/myObj") | x = app.myObj.get() | Returns object | |
| | | | |
GlobalContext.delete("/app/myObj") | _gc.delete('/app/myObj') | Deletes object | |
post is not a valid operation on Objects.
Nested
Value manipulation may be nested for
get() and
condGet(); all other operations support up to one level of value manipulation. Parameters for value manipulation are delimited by a slash (/).
For example:
Map<String, String> innerMap0 = new HashMap<String, String>();
innerMap0.put("im0a", "a");
innerMap0.put("im0b", "b");
Map<String, String> innerMap1 = new HashMap<String, String>();
innerMap1.put("im1c", "c");
innerMap1.put("im1d", "d");
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
list.add(innerMap0);
list.add(innerMap1);
Map<String, List<Map<String, String>>> map
= new HashMap<String, List<Map<String, String>>>();
map.put("x", list);
GlobalContext.put("/config/nested", map);
assertEquals("b", GlobalContext.get("/config/nested#x/0/im0b"));
assertEquals("c", GlobalContext.get("/config/nested#x/1/im1c"));
Other type handlers (pending)
XML
// document represents:
// <customers>
// <customer><name>Abe</name></customer>
// <customer><name>Beth</name></customer>
// </customers>
GlobalContext.put("/request/myXml", document);
assert GlobalContext.get("/request/myXml#/customers/customer[0]/name/text()").equals("Abe");
Fragments supported by the
XML type handler:
JavaBean
Customer customer = new Customer();
customer.setName("Cathy");
GlobalContext.put("/request/myBean", customer);
assert GlobalContext.get("/request/myBean#name").equals("Cathy");
Fragments supported by the
JavaBean type handler:
| Fragment | Description |
| #<propertyName> | Property name. Handled by getter/setter or field. Supports get() and put(). |
Zone handler interface
Zone handlers are responsible for basic storage operations. Note that
post() implies an append operation, which is a function of the type handler -- not the zone handler.
public interface ZoneHandler {
public String getName();
public <T> T get(String path);
public boolean containsKey(String path);
public List<String> list(String prefix);
public <T,V> T put(String path, V value);
public void delete(String path, boolean deleteChildren);
}
Zone handlers are registered by putting the fully-qualified class name in the GC at
/config/zoneHandlers/<zoneName>.
Type handler interface
Type handlers are responsible for value manipulation. In order to maintain support for nested value paths, implementations must "consume" their corresponding parameter from the value path and advance the
valuePathOffset in
parsedUri. See the source for
zero.core.context.types.MapTypeHandler or
zero.core.context.types.ListTypeHandler for examples.
public interface TypeHandler {
public <T, V> T get(ParsedURI parsedUri, V storedValue);
public <T,V,X> T put(ParsedURI parsedUri, V newValue, X storedValue);
public <T,V,X> T post(ParsedURI parsedUri, V newValue, X storedValue);
public <T, X> T delete(ParsedURI parsedUri, X storedValue);
}