|
|
|
Global context
The global context, as the name implies, provides a central construct for applications to store and retrieve environment information. Scripts in Project Zero run in the context of an HTTP request, the request zone therefore provides access to the HTTP request data. All the information the application has been configured with (including port numbers, directory locations and dependencies) is provided through the config zone. APIs for the global context are available for Java, Groovy and PHP. See the Global context reference for a list of the attributes available, by default, in the global context. The following sections of this article provide information about global context and the APIs:
Note to M1/M2/M3 users: The global context has changed since the M1, M2 and M3 builds. See the migration and other information about this change, provided in the Changes in the global context article.
The Zones
Project Zero provides a default set of zone handlers for applications. Each zone handler provides different lifetime and scope behavior. The Global Gontext can be extended with additional zone handlers as described in the Extending the global context section.
Config zone
Data in the /config zone is generally loaded from configuration files. This data is globally visible and is available for the lifetime of the application.
App zone
Data in the app zone is globally visible and is available for the lifetime of the application.
Request zone
Data in the request zone is visible to the thread that is processing the HTTP request and is available for the duration
of request processing (until the response is sent back to the client).
User zone
Data in the user zone is visible to all threads that are processing requests belonging to the same HTTP session (determined by the zsessionid cookie).
At this time, the default user zone is based upon in-memory persistence. Data is available until the application is restarted. While data is saved at the end of each request, it is not guaranteed to be saved before the next request comes in. Additional commands are available for direct manipulation of the user zone:
| Command | Description |
zpost("/user#save", true) | Force an immediate synchronous save of the user zone. |
zpost("/user#invalidate", true) | Invalidate the session. |
Event zone
Data in the event zone is visible to the thread on which a handler was dispatched. Data is available for the duration
of the event handler procedure call. Changes made to the event zone by one handler are not visible to other
handlers of the same event.
Accessing the global context
The global context is available from all parts of a Zero application. How you access the global context varies depending on the language of the artifact.
Global context APIs
Values in the GC are addressed with a "key" reference, which has the following syntax: /<zone>/<path>[#<value_path>].
The GC APIs are summarized with examples based on the following GC contents:
/config/foo/a = b
/config/foo/x = y
/config/map = ["a" : "b", "x" : "y"]
| Syntax | Comments/examples |
T zget(String uri [, T defaultValue]) | Returns the value stored at uri; if none, then returns null or defaultValue.
zget("/config/foo/a") == "b" zget("/config/foo") == null (M2: threw an exception) |
boolean zput(String uri, V value) | Create/replace the stored value with value. Elements of a stored value may be replaced with Value "pathing" appended to the uri. |
boolean zpost(String uri, V value) | Create/append value to the stored value. Appends are handled by a TypeHandler; details are summarized in the Value "pathing" section. |
void zdelete(String uri [, boolean deleteChildren]) | Deletes the specified key; optionally removes all keys that start with uri. |
| | |
boolean zcontains(String uri) | Indicates whether a value is stored at uri.
zcontains("/config") == true zcontains("/config/map#a") == true |
List zlist(String uriPrefix [, boolean includePrefix]) | Returns a list of children of uriPrefix
zlist("/") == ["/config"] zlist("/config/foo") == ["/config/foo/a", "/config/foo/b"] zlist("/config", false) == ["foo", "map"] |
List zlistAll(String uriPrefix [, boolean includePrefix]) | Returns a list of all GC URIs that start with uriPrefix
zlistAll("/") == ["/config/foo/a", "/config/foo/x", "/config/map"] |
String zdump(String uri) | Renders the content of all keys that start with uri using toString(). |
| | |
Map zputs(String uriPrefix, Map payload) | Convenience method for creating GC entries from key/value pairs in the payload.
map.put("w", "z") zputs("/config/bar", map) zget("/config/bar/w") == "z" |
Value "pathing"
Value pathing is a compact means of referring to elements within a stored value. Value paths are specified by appending fragment identifiers to the key. Details about the fragment (syntax and meaning) are dependent on the type handler associated with the value.
Before we get into the details of type handlers, it may be useful to summarize the support for zput and zpost within the GC APIs:
-
zput() means create (if not defined) or replace (otherwise)
-
zpost() means create (if not defined) or append (otherwise)
Exception conditions
The GC APIs throw an IllegalArgumentException if the uri is ill-formed. A valid GC uri has the following characterics:
Maps
The Map type handler supports the #<key> fragment. It refers to the value mapped to <key>.
| Java API | Groovy API | Description | Can create entry |
zput("/app/myMap", map) | app.myMap = map | Create/replace existing map | |
zput("/app/myMap#foo", "bar") | app.myMap['foo'] = 'bar' | Add/replace entry; map must exist | |
| | | | |
zpost("/app/myMap", map) | zpost("/app/myMap", map) | Create/merge into existing map; replaces existing keys | |
| | | | |
zget("/app/myMap") | x = app.myMap[] | Returns the map | |
zget("/app/myMap#foo") | x = app.myMap['foo'] | Returns entry at key foo | |
| | | | |
zdelete("/app/myMap") | zdelete("/app/myMap") | Deletes map | |
zdelete("/app/myMap#foo") | zdelete('/app/myMap#foo') | Removes entry | |
Note: Posts to a Map entry are not supported (e.g. zpost("/app/myMap#foo", "bar")).
Lists
The List type handler supports the #<N> fragment. It refers to the N-th element in the list.
| Java API | Groovy API | Description | Can create entry |
zput("/app/myList", list) | app.myList = list | Create/replace existing list | |
zput("/app/myList#0", "bar") | app.myList[0] | Set/replace list element; list must exist | |
| | | | |
zpost("/app/myList", list) | zpost("/app/myList", list) | Create/append to list | |
zpost("/app/myList", "bar") | zpost("/app/myList", "bar") | Append to list; list must exist | |
| | | | |
zget("/app/myList") | x = app.myList[] | Returns the list | |
zget("/app/myList#0") | x = app.myList[0] | Returns 0th element | |
| | | | |
zdelete("/app/myList") | zdelete("/app/myList") | Deletes list | |
zdelete("/app/myList#0") | zdelete("/app/myList#0") | Removes first entry | |
FirstElementList
FirstElementList is a variant of List that supports access to the first element as a simple object (without specifying the index). This simplifies handling of values, like query parameters, that are properly stored as lists but are most commonly retrieved as single values.
Within the default zones, only /request/params/<parmName>, /request/headers/in/<headerName>, and /request/headers/out/<parmName> are instances of FirstElementList.
The FirstElementList type handler supports the #<N> fragment. It refers to the N-th element in the list.
| Java API | Groovy API | Description | Can create entry |
zput("/request/params/p", list) | request.params.p = list | Create list; list does not exist | |
zput("/request/params/p#*", list) | request.params.p['*'] = list | Replace list; list must exist | |
zput("/request/params/p", "bar") | request.params.p = "bar" | Set/replace first element; list must exist | |
zput("/request/params/p#0", "bar") | request.params.p[0] | Set/replace list element; list must exist | |
| | | | |
zpost("/request/params/p", list) | request.params.p << list | Create list; list does not exist | |
zpost("/request/params/p#*", "bar") | request.params.p['*'] << "bar" | Append to list; list must exist | |
| | | | |
zget("/request/params/p") | x = request.params.p[] | Returns first element | |
zget("/request/params/p#0") | x = request.params.p[0] | Returns first element | |
zget("/request/params/p#*") | x = request.params.p['*'] | Returns full list | |
| | | | |
zdelete("/request/params/p#*") | zdelete("/request/params/p#*") | Deletes list | |
zdelete("/request/params/p#0") | zdelete("/request/params/p#0") | Removes first entry | |
Objects (default)
Note: post is not a valid operation on Objects.
Nested
Value manipulation can be nested for zget() zcontains(); all other operations support up to one level of value manipulation. Parameters for value manipulation are delimited by a slash (/) as shown in the following 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);
zput("/config/nested", map);
assertEquals("b", zget("/config/nested#x/0/im0b"));
assertEquals("c", zget("/config/nested#x/1/im1c"));
Commands
The Command type handler delegates all operations to the implementation of Command stored in the accessed GlobalContext location. This allows you to provide your own implementation of Command and implement the actions to be taken on a GET, PUT, POST, and DELETE. For convenience, Zero provides a zero.core.context.types.commands.BaseCommandImpl with implementations of GET, PUT, POST, and DELETE which return Context.NO_ZONE_UPDATE. Returning Context.NO_ZONE_UPDATE causes no operation to take place.
Because the Command type already has a registered type handler, you do not need to add new type handlers when providing your own Command implementations.
Commands can be used to implement your own behavior. For example, you could defer evaluation of an expensive value until a get is actually called as follows:
GlobalContext.zput(Request.remoteHost, new BaseCommandImpl() {
Object value = null;
public <T> T get(ParsedURI parsedUri) {
// null is not a valid return value for InetAddress.getHostName()
if (value == null) {
value = req.getRemoteHostName();
}
return (T) value;
}
});
assertEquals(req.getRemoteHostName(), GlobalContext.zget(Request.remoteHost));
Another usage of Command would be to implement post to carry out your own custom action.
When using the default implementation of Command provided by BaseCommandImpl Command objects can not be replaced or deleted from the global context. If you need this behavior, you can override the corresponding methods in Command to allow it. For example, the following implementation allows the Command object to be deleted:
@Test
public void testDeleteableCommand() {
GlobalContext.zput("/test/deleteablecmd", new BaseCommandImpl() {
Object value = null;
public <T> T get(ParsedURI parsedUri) {
if (value == null) {
value = "original value";
}
return (T)value;
}
@Override
public <T> T delete(ParsedURI parsedUri) {
return (T) Context.NO_VALUE;
}
});
assertEquals(true, GlobalContext.zcontains("/test/deleteablecmd"));
GlobalContext.zdelete("/test/deleteablecmd");
assertEquals(false, GlobalContext.zcontains("/test/deleteablecmd"));
}
Extending the global context
You can extend the global context with additional zone and type handlers. Zone handlers store the data and are responsible for scope and lifetime of that data. Type handlers handle value "pathing".
Zone handlers
The global context APIs treat the first path element of an access key as the name of a ZoneHandler ( /<zone>/<path>[#<value_path>] ). GC APIs then delegate store/retrieve operations to the corresponding zone handler.
Zone handlers are Java implementations of zero.core.context.zones.ZoneHandler . Zone handlers are registered via configuration, such as:
/config/zoneHandlers += {
"user" : "zero.core.context.zones.UserZoneHandler"
}
Zone handlers may also register for deactivation on an event. The DeactivateHandler is provided as a convenience utility for this purpose. For example, the default user zone handler is deactivated at the end of the "request" lifecycle as a result of the following configuration snippet:
/config/handlers += {
"events" : "requestEnd",
"handler" : "zero.core.handlers.DeactivateHandler.class",
"instanceData" : { "zoneName" : "user" }
}
Type handlers
Type handlers are responsible for value manipulation, including appends. Type handlers are Java implementations of zero.core.context.zones.TypeHandler
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 specific examples.
Type handlers are registered by appending type/handler implementation key/value pairs to the map in the global context at /config/typeHandlers. For example, the handler implementation (zero.core.context.types.CommandTypeHandler) for the Command type (zero.core.context.types.commands.Command) is specified in configuration as:
/config/typeHandlers += {
"zero.core.context.types.commands.Command" : "zero.core.context.types.CommandTypeHandler"
}
|