Migration notes for M3
This page summarizes key changes from the previous milestone.
Configuration
The configuration syntax changed for M3. The new syntax is based upon a simple grammar of Global Context URIs and
JSON data structures. This change was intended to improve clarity by leveraging a known data format.
The new syntax is documented on the
Configuration page of the Developer's Guide. This section summarizes changes from the previous syntax.
Tips for porting from M2
| Semantic | Old syntax | New syntax |
| Value set | [/config/http] port=8080 | /config/http/port=8080 |
| List append | [/config/bindings/.groovy[]] zero.core.groovysupport.bindings.DefaultBindings | /config/bindings/.groovy+=["zero.core.groovysupport.bindings.DefaultBindings"] |
| Variable set | [@properties] myProp=/foo/bar | myProp="/foo/bar" |
| Variable reference | [/config/test] value=${myProp}/bat | /config/test/value="${myProp}/bat" |
| Includes | [@include ${zero.core}/f.config] formLoginPage=/login | @include "${/config/dependencies/zero.core}/f.config" {"formLoginPage":"/login"} |
Other changes
- Zero no longer fires a
config event during config load.
- Dependency information is stored in a different way:
-
/config/dependencies/list is now at /config/dependencies
-
/config/dependencies#dep is now at /config/dependencies/dep
- Resolved properties have changed to GC value references. For example,
${zero.core] from the old syntax is now specified as ${/config/dependencies/zero.core}.
GC changes
The GC has been modified to simplify configuration processing. The details are probably interesting only to those who deal with the internals of Zero, such as the run-time team. Other users are not affected by these changes.
First, the GC APIs and internal constructs have been separated. Instances of the new
zero.core.context.Context represent all the internal constructs, including zone and type handlers;
zero.core.context.GlobalContext is still a set of static APIs, but they now operate on an instance of
Context set with
GlobalContext.setContext(Context).
Second, the new
zero.core.config.ConfigLoader contains a set of utility methods for loading
.zeroresolved.properties files and processing config files. An example of creating a new GC and loading all config files (application then dependencies) is shown below:
GlobalContext.setContext(new Context());
ConfigLoader.loadResolvedProperties(_applicationRootDirectory);
List<String> listOfDependencies = zget(Config.Dependencies._self);
for (String dependency : listOfDependencies) {
String dependencyRoot = zget(Config.Dependencies._self + "/" + dependency);
File configFile = new File(dependencyRoot + "/config/zero.config");
ConfigLoader.loadFile(configFile.getAbsolutePath());
}
Global Context
Objectives for the changes:
- 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.
Summary of changes
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.
Tips for porting from M2
- 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[].