|
|
|
Changes in the global context
The global context has changed to improve usability and performance. This article summarizes changes in the global context relative to M1, M2 and M3.
Changes since M3
Objectives
- Provide minor updates to the API to make Global Context accessors behave consistently.
- Changed how the TypeHandlers walk GlobalContext URI
- Simplified security related Global Context URI
Updates to the Global Context accessors
Added zlistAll method
A new Global Context accessor method called zlistAll() has been added that will list all GC URIs that begin with the specified prefix.
Support for nested updates
The GC APIs for updating a stored value, namely zput(), zpost() and zdelete(), can now use value paths.
Modified zput() and zpost() return values
In M3, these APIs returned the stored value that was replaced/modified.
The concept of the "replaced" value is not so intuitive with the new support for nested updates. These APIs now return a boolean, which indicates whether the stored value was modified.
Throw exception for zlist with value path
The zlist() API now supports regular path only. Invoking zlist with a value path results in an IllegalArgumentException.
Modified behavior of zcontains() on a zone
In M3, zcontains("/zone") returned true if the zone was activated. That support was inconsistent with the behavior of zcontains for other URIs.
zcontains("/zone") now returns true if there is a value stored at /zone.
Modified FirstElementList support
Value path support has changed for FirstElementLists. "*" is now required in order to address the full list; otherwise the value path is applied to the first element.
The changes are summarized in the following updated table:
| 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")
replaces zput("/request/params/p#0", "bar") | request.params.p['*/0'] | Set/replace list element; list must exist | |
zput("/request/params/p#p1", "bar") | request.params.p['p1'] = "bar" | Assumes a map stored at the first element; set/replace the value of "p1" with "bar" in that map | |
| | | | |
zpost("/request/params/p", list) | zpost("/request/params/p", list) | Create list; list does not exist | |
zpost("/request/params/p#*", "bar") | zpost("/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")
replaces 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")
replaces zdelete("/request/params/p#0") | zdelete("/request/params/p#*/0") | Removes first entry | |
Modified Groovy support for GC access
Reduced GPath-like support to just read/write operations
Removed support for append, which was implemented with the left-shift operator (<<). The mechanism worked when there was no value path, but didn't work with value path, so we've removed the (partial) support.
The z* APIs are still available.
Removed the key() method from GCAccessor
The mechanism couldn't support value path, so the support has been removed.
Modified ConfigLoader APIs
zero.core.config.ConfigLoader contains a set of static methods. In M3, these methods were not consistent in argument types for files: sometimes used String, sometimes used File.
All of the APIs now take String for file references.
Added single-shot GC initialization
zero.core.config.ConfigLoader.initialize(String root [, Context context]) consolidates the following steps into one API:
- Load
.zeroresolved.properties
- Load all of the config files in order
This API simplifies test cases that use the GC without the ApplicationLauncher.
Added ListFactory and MapFactory utilities
ListFactory
List list = ListFactory.create("p1", "p2");
is equivalent to
List list = new ArrayList();
list.add("p1");
list.add("p2");
MapFactory
Map map = MapFactory.create("p1", "v1", "p2", "v2");
is equivalent to
Map map = new HashMap();
map.put("p1", "v1");
map.put("p2", "v2");
Type Handler updates
Modified TypeHandler interface
zero.core.context.types.TypeHandler now uses generics. This simplifies implementations because you no longer need to check/cast the stored value to the corresponding type.
public class MapTypeHandler implements TypeHandler<Map> {
public <V> V get(ParsedURI parsedUri, Map storedMap) {
...
Changed the mechanism for "walking" through types/type handlers
In M3, the GC APIs relied solely on the value path to determine when to apply another type handler. Once the last segment of the value path had been "consumed" by a type handler, then the value was complete.
The M3 approach didn't work for Commands and FirstElementLists, since those types handlers can return modified values even without a value path. The new mechanism loops through type/type handler processing until the output matches the input. That is, we continue to walk until the result stops changing.
Modified "no store" response from Command implementations
In M3, Command implementations returned Context.NO_STORE to indicate that the GC should not change the stored value. This was useful, for example, when using a Command that processed requests dynamically.
Commands now throw a zero.core.context.UpdateDeniedException for the same effect; Context.NO_STORE has been removed.
Modified security-related Global Context(GC) URIs
Some of the security-related GC URIs were modified, in keeping with the convention of separating GC data and URIs.
| M3 | New |
| /request/subject#remoteUser | /request/subject/remoteUser |
| /request/subject#users | /request/subject/users |
| /request/subject#groups | /request/subject/groups |
| /request/subject#roles | /request/subject/roles |
| /request/subject#remoteCodePrincipals | /request/subject/remoteCodePrincipals |
Modified ZoneHandler interface
The delete method now operates on one entry per request; the option to also delete children within the zone handler has been removed. This change was required in order to give Command implementations control over whether they can be deleted. That is, deletes are now driven through the type handlers.
The list(String path) method now returns all keys that start with path.
Changes since M2
Objectives
- Provide a common GC API across programming languages (Java, Groovy, PHP). We're close already. By renaming methods in a few places, we'll be the same.
- Clarify the use of the
GCAccessor, which is the Groovy shortcut for GC access. Use of the GCAccessor is optional.
- Improve usability of the GC APIs. For example:
- Simpler
get(): GlobalContext.zget(uri) will return null if the uri is not found (rather than throwing an IllegalArgumentException).
- Simpler "walking":
GlobalContext.zlist(uri) will return a list of child URIs. Thus, zlist() will be useful for "walking" through the GC.
Revised GC APIs
The GC APIs now have a "z" prefix as a simple means of reducing namespace collisions.
The following sections describe the new syntax and behavior changes in the GC API. We'll use 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]) | zget("/config/foo/a") == "b" zget("/config/foo") == null (M2: threw an exception) |
List zlist(String uriPrefix [, boolean includePrefix]) | Returns a list of children of uriPrefix (M2: returned all uris that started with uriPrefix).
zlist("/") == ["/config"] zlist("/config/foo") == ["/config/foo/a", "/config/foo/b"] zlist("/config", false) == ["foo", "map"] |
boolean zcontains(String uri) | Was containsKey(uri) in M2 Handles value path (M2: no value path) =zcontains("/config") == true zcontains("/config/map#a") == true |
T zpost(String uri, V value) | Same as M2. |
T zput(String uri, V value) | Same as M2. |
Map zputs(String uriPrefix, Map payload) | Was available in Groovy for M2. map.put("w", "z") zputs("/config/bar", map) zget("/config/bar/w") == "z" |
void zdelete(String uri [, boolean deleteChildren]) | Same as M2. |
String zdump(String uri) | Same as M2. |
Java: Using static imports
Java code can be a clean demonstration of the GC APIs by using static imports, as follows:
Java example
import static zero.core.context.GlobalContext.*;
public class MyHandler {
public void onGET() {
String qvalue = zget("/request/params/qname");
}
}
Groovy: Added method bindings for GC APIs
All .groovy scripts, as well as classes that implement ZeroObject , receive bindings for the GC APIs. So, there's no need to import GlobalContext.
Revised exception conditions
In M2, GlobalContext.get(uri) threw an IllegalArgumentException if uri was not found. Most of the other APIs were more friendly, returning default/empty values, even if uri was ill-formed.
In M3, zget(uri) returns null if uri is not found. This is equivalent to zget(uri, null).
Another significant change in M3 is that all of the GC APIs throw an IllegalArgumentException if the uri is ill-formed. A valid GC uri has the following characterics:
Groovy: Clarified GCAccessor
GCAccessor is the support behind the GPath-like interactions with the GC. An example from M2:
def id = request.params.userId.get()
request.status = 202
This approach was rather confusing because we tried to use the GCAccessor to represent data, but it didn't always work that way (note the occasional need for .get()). Also, there were other use cases where we wanted to extract the GC URI or an accessor object from the data (ref. [[http://www.projectzero.org/wiki/bin/view/Community/JasonsBlog/BlogEntry5][.toPath() and .toGPath()]), which stretched the intuition.
For M3, GCAccessor has been reworked to be just that: An accessor object. It's been integrated into the Groovy syntax, as summarized here:
| GC API | Groovy shortcut |
zget(uri) | Always ends with brackets; empty brackets are valid. def m = request.method[] def id = request.params.userId[2] |
zget(uri, defaultValue) | def id = request.params.userId.zget('anonymous') |
zlist(uriPrefix) | request.headers.in.zlist() request.headers.in.zlist(true) |
zcontains(uri) | request.headers.in.zcontains('X-METHOD-OVERRIDE') |
zpost(uri) | request.headers.out.MyHeader << ['MyValue'] |
zput(uri) | request.method = 204 |
zputs(uri, payload) | request.headers.out.zputs([MyHeader : 'MyValue']) |
zdelete(uri) | user.counter.zdelete() |
zdump(uri) | request.zdump() |
The following property bindings are GCAccessor objects available by default to all .groovy scripts and class implementations of ZeroObject:
- app
- config
- event
- headers
- request
- user
Additional bindings may be registered using the same method as for M2.
A more complete set of examples are available in the zero.core.tests unit test cases.
Groovy: Removed _gc binding
Use method bindings (e.g. zget('/request/method')) or GCAccessor bindings (e.g. request.method[]) instead.
Zone handlers
Changes for ZoneHandler implementations:
- No longer implement
containsKey(); this is now handled in the GlobalContext implementation.
- Must implement
isAvailable(). Currently used to control access; may be removed early next week. Depends on whether the zone handlers can be self-protecting on the normal ZoneHandler APIs without inadvertent auto-activation.
Changes for ThreadLocalZoneHandler implementations:
- That interface has been removed; replaced with
ActivatableZoneHandler interface.
Porting tips
- Search .groovy and .gt files for instances of
.get(); replace with [] for GC accessors.
- Search .groovy and .gt files for
.condGet(uri, null); replace with [].
While debugging, if you find that a variable comes through with what looks like a path instead of a value (e.g. /request/status instead of 200), then you're probably missing a [] on the property binding. That is, you probably have def x = request.status where you should have def x = request.status[].
Changes since M1
If you have been using the M1 version or daily builds of Project Zero prior to the M2 version, you will notice some significant changes in the way the global context is formatted and how it functions. Previously, the global context was a hierarchical data store. So, invoking a global context API resulted in traversing a hierarchy of SubContexts to the specified leaf node (the value you specified).
To simplify this structure and improve performance, the M2 version of the Project Zero global context uses a "flat" data store (similar to a Map). There is no longer a hierarchical storage structure. The following changes for the M2 global context are discussed in this article:
Configuration file changes
The syntax of the configuration files has not changed, but the way in which the data is stored has changed. Configuration data is now stored in the /config zone. The following examples show how the data configuration has changed and how entries must be changed:
- Before
-
[/app/handlers]
- After
-
[/config/handlers]
Maps are also flattened when placed into the new global context. The following example now provides different output as shown in the following Before and After descriptions:
[/app/foo]
bar = x
- Before
- A Map was stored at
/app/foo.
- After
- The value
x is stored at /app/foo/bar.
"Value pathing" changes
Value pathing, which is a compact means of accessing sub-elements of a stored value, has changed for the M2 global context. For starters, the syntax has changed:
- Before
- The global context supported implicit value paths. This means that
/app/foo/bar could refer to either of the following:
- A value stored at
/app/foo/bar
- The value at
bar in a Map stored at /app/foo
- After
- The new global context uses explicit value paths. Using this same example,
/app/foo/bar now refers to a value stored at that key. /app/foo#bar refers to the value at bar in a map stored at /app/foo.
Details about default value pathing for common object types are summarized in the sections of the Global context article linked in the following list:
Request parameter and header changes
Request parameters and headers are stored as lists in the global context. Changes were made in M2 to simplify access to the first element:
- Before
-
/request/param/foo/0
- After
-
/request/param/foo
This support is realized through a FirstElementList type handler.
SubContext changes
The SubContext no longer exists in the global context but you can use zlist() as an alternative. The following Before and After samples in Java provide examples:
- Before
-
SubContext headers = GlobalContext.condGet(Request.Headers.out, null);
if (headers != null) {
for (String name : headers.keys()) {
Object value = GlobalContext.get(headers, name);
- After
-
List<String> name = GlobalContext.zlist(Request.Headers.out, false);
for (String name : names) {
Object value = GlobalContext.zget(Request.Headers.out + "/" + name);
This information was not previously accessible using the global context accessors in PHP. The equivalent sample in PHP would now look like the following
<?php
$header_keys = zlist('/request/headers/out', false);
foreach($header_keys as $header_key) {
$header_value = zget('/request/headers/out/'.$header_key);
}
?>
More information on the latest global context accessors in PHP is documented at PHP global context access.
Zone handler support changes
The way the global context implements zone handlers has changed for M2. To see detailed information on how zone handlers are now implemented, see the Zone handlers section of the Global context article in the Core Developer's Guide.
Type handler support changes
The way the global context implements type handlers has changed for M2. To see detailed information on how type handlers are now implemented, see the Type handlers section of the Global context article in the Core Developer's Guide.
|