Model services
Documentation for the JSON version of the REST interface may be accessed at <serverRoot>/openAPI.html. It also gives you the opportunity to try it out. For example: http://localhost:7080/training/openAPI.html.
Model Services provide API access to the Business Model for REST style create, read, update, delete, and invoke. They are available at <serverRoot>/<format>/<objectType>/<id>?<parameters>
along with an HTTP Method to indicate the CRUD (create, read, update, delete) verb. There are a number of <format> endpoints.
- rest/json - (recommended) the current standard JSON endpoint
- json - legacy JSON endpoint - can be made to behave like the rest/json endpoint through querystring parameters but uses older standards by desfault. For example, it will handle enumerations by object rather than by value.
- rest/xml - the current standard XML endpoint
- xml - legacy XML endpoint
- web - SOAP endpoint
- api/... - versioned apis as described in Model Services Versioning
For example, to use the rest/json endpoint to retrieve (read) all contacts with a last name of Lamont and display their first name, last name, and addresses in JSON, issue the following GET request in the address bar of your browser:
http://localhost:7080/nexj/rest/json/Person?$indent&$syntax=js&attributes=firstName lastName addrs.city addrs.state addrs.zip&where=lastName == "Lamont"
[
{
"$class": "Person",
"$oid": "101523C22B0D234FEBA0609E945E0D3834",
"lastName": "Lamont",
"addrs": [
{
"$class": "Address",
"$oid": "100ACC40CA4EA44D09BE5BBF6D1C221847",
"state": "ON",
"zip": "M2M 2S9",
"city": "Toronto"
},
{
"$class": "Address",
"$oid": "108AB688E1BC7841BB90313D64E429792B",
"state": "ON",
"zip": "M4N 1S5",
"city": "Toronto"
}
],
"firstName": "Tim"
},
{
"$class": "Person",
"$oid": "102D841BEB687949C68FAFE87289EEB1BA",
"lastName": "Lamont",
...
]
$class and $oid
The $class
and $oid
attributes of an object are always returned.
They are the class name of the object and unique identifier respectively.
Request Structure
Model Services are available at <serverRoot>/<format>/<objectType>/<id>?<parameters>
along with an HTTP method to indicate the CRUD verb.
serverRoot
The host and context root of the server. For example: http://localhost:7080/training
This is defined in your environment file in the httpURL property.
format
Defines the return type for the request. Formats supported include "rest/json", "rest/xml", "json", "xml", and "web" for a SOAP encoding. These endpoints all support the REST style requests documented here. All examples in this documentation use the json format. To see the effect of another protocol on the example output, replace json
with xml
or web
. There are additional supported protocols, including soap and text, but they use a different API style called Generic RPC and are not covered here.
objectType
Specifies the object type to work on, such as Person, Household, or Account. The object's namespace is required if it has one. For example: rest/json/ilm:Lead?$indent
id
An optional unique identifier for the item to work on. If there is no id in the URL, the request will work on a collection. If there is an id in the URL, the request will work on a specific instance. For example: http://localhost:7080/training/json/Person/101DBCCB4F0B4E4345BDD1C68ECE20DBBD?$indent
parameters
The following parameters employ the same syntax as other object queries. That is, they use the same syntax that is used when creating a script for a read operation.
- attributes
- where
- orderby
- count
- offset
A simplified syntax is available if $syntax=js
is included as a parameter. This is the default on the rest/json and rest/xml endpoints.
$indent
Can improve the readability of the output.
The $indent
parameter is included in many of the following examples. It is only for readability and is not required.
$syntax
$syntax=js uses a simplified syntax for attributes and where clauses. It is the default on the rest/json and rest/xml endpoints.
$syntax=scheme uses scheme syntax as documented in the Class'read event.
attributes
If you don't specify an "attributes" parameter, all public primitive attributes are returned. If you do specify a list of primitive attributes in the attributes parameter, then only those attributes will be returned along with the message type and oid (unique identifier), which are always returned.
If no default $syntax is set on an endpoints component with, for example, <Property name="syntax">js</Property> then, for backward compatibility, outer brackets are required and "dot" notation is not supported. For example, attributes=(firstName lastName (addrs city (type name)))
is valid
If either $syntax=js or $syntax=scheme is specified, then the outer brackets are not required and a mix of dot notation or brackets are supported. For example,
rest/json/Person?$indent&attributes=firstName lastName genderCode
returns:
[
{
"$class": "Person",
"$oid": "10DB4B54A113294254B68D6083376E34E9",
"lastName": "Lamont",
"firstName": "Dionne",
"genderCode": "F"
},
....
]
Associated objects can also be retrieved using nested lists in the attributes parameter. To do this, specify the non-primitive attribute name followed by a list of one or more attributes to retrieve. For example:
rest/json/Person?$indent&attributes=firstName lastName (addrs city state zip)
returns:
[
{
"$class": "Person",
"$oid": "100BFAA7399522406195DE457CD94C519B",
"lastName": "Lamont",
"addrs": [
{
"$class": "Address",
"$oid": "1090704C0AA3A04A539FDF1B31B10798E2",
"state": "ON",
"zip": "M2M 2S9",
"city": "Toronto"
},
{
"$class": "Address",
"$oid": "10C4317637F4AE432993BD488C1FC7D563",
"state": "ON",
"zip": "M5G 2L3",
"city": "Toronto"
}
],
"firstName": "Tim"
},
...
The attribute clause could also be written with "dot" notation as attributes=firstName lastName addrs.city addrs.state addrs.zip
Dot notation is convenient and familiar, but if you are addressing many sub-attributes the bracket notation is less typing. Compare:
attributes=firstName lastName addrs.city addrs.state addrs.zip addrs.type.name
attributes=firstName lastName (addrs city state zip (type name))
If a complex attribute itself has a complex attribute, then these nestings can continue. For example:
firstName lastName (addrs city state zip (type name))
Attributes can also be retrieved polymorphically. That is, attributes that only exist on subclasses can be still retrieved from reading the base class. (see the examples below)
Attribute names in the where and order by parameters may take the simple form <attributeName>
or alternatively (@ <attributeName>)
. The second form, using @ notation, is recommended. In this case, the "@
" represents the current instance and may be used to traverse arbitrarily long association paths. For example, on a User you might specify:
(= (@ person homeAddress city) "Toronto")
Example response
[{
"$class": "Person",
"$oid": "10031C7A7EF46D45ADBC72090BCCD04825",
"lastName": "Cotherman",
"addrs": [
{
"$class": "Address",
"$oid": "10EE388953279847C6B7DB63CACAB774C0",
"state": null,
"zip": null,
"type": {
"$class": "AddressType",
"$oid": "1000000000000010008000CEEDED004000",
"name": "Business"
},
"city": null
}
],
"firstName": "Joseph",
"initials": null
}
]
Multiple chaining is supported in where clauses. So, in an "js" clause, both of the following options are valid and equivalent:
(@ person homeAddress city)
where=person.homeAddress.city == "Toronto" syntax
To traverse association paths, you can wrap associated objects in brackets and then list their primitive and associated attributes. This can be done recursively. For example:
http://localhost:7080/training/json/Person?attributes=(firstName lastName initials (addrs city state zip (type name)))&$indent
results in:
Example response
{
"$class": "Person",
"$oid": "10031C7A7EF46D45ADBC72090BCCD04825",
"lastName": "Cotherman",
"addrs": [
{
"$class": "Address",
"$oid": "10EE388953279847C6B7DB63CACAB774C0",
"state": null,
"zip": null,
"type": {
"$class": "AddressType",
"$oid": "1000000000000010008000CEEDED004000",
"name": "Business"
},
"city": null
}
],
"firstName": "Joseph",
"initials": null
},
Advanced syntax for downcast and annotations
Two special (advanced) syntax structures exist for downcast and annotations.
Attribute downcast syntax (@@) - A polymorphic read is used to proactively load subclass-specific attributes when reading from a base class. For example, the following will only retrieve the homePhone attribute for Entity records that are of type Person, otherwise it is omitted.
http://localhost:7080/training/json/Entity?attributes=(fullName lastName (@@ Person firstName homePhone))&$indent
Annotations syntax - Creates ad hoc calculated attributes
To show how this syntax works, the following example call will return the following:
- The lastName attribute
- A calculated attribute, _lnLen, which is the length of the last name
- A calculated attribute, _fullName, which concatenates the firstName and lastName attributes.
Underscore characters are used here to avoid name collisions.
http://localhost:7080/training/json/Person?attributes=(lastName (: _lnLen (string-length (@ lastName))) (: _fullName (string-append (@ lastName) (@ firstName))))&$indent
Aggregate functions - Expand the annotations syntax
The annotations syntax can be further expanded with the use of aggregate functions. These functions apply to subcollections within instances. Supported operators include count
, average
, min
, max
, and sum
.
For example:
http://localhost:7080/training/json/Person?attributes=(firstName lastName (: _countryCnt (count (@ addrs country))) (: _uniqueCountryCnt (count (unique (@ addrs country)))) (: _nullCodeCnt (count (null? (@ addrs zip)))) (: _avg (average (string-length (@ addrs city)))) (: _min (minimum (string-length (@ addrs city)))) (: _max (maximum (@ addrs city))) (: _sum (sum (string-length (@ addrs city)))))&$indent
where
The where clause. The following examples illustrate the use of associations, reverse associations and qualified associations.
Simple where not using the @ syntax - (and ( = firstName "Tim") (= lastName "Lamont"))
Simple where using the @ syntax - (and ( = (@ firstName) "Joe") (= (@ lastName) "Test"))
Simple where using the js syntax - firstName=="Joe" and lastName == "Test"
To use the "js" syntax in a browser for GET requests, some characters such as && and || for AND and OR must be urlencoded and escaped as %26%26 and %7C%7C respectively. Alternatively, simply use and or or (lower case).
Any
The "any" operator reduces the multiplicity of an association to 1
This allows you to create queries that would be difficult or impossible to express using only joins. In this example, the query returns all people who have at least one address in Toronto and at least one in Richmond Hill.
(and (any (= (@ addrs city) "Toronto")) (any (= (@ addrs city) "Richmond Hill"))) ; two part association path
Or, using "js" syntax:
any(addrs.city == "Toronto") && any(addrs.city == "Richmond Hill")
In contrast, nothing would be returned if the any operator were left out: (and (= (@ addrs city) "Toronto") (= (@ addrs city) "Richmond Hill"))
since this statement requires that a single city attribute have both the value of "Toronto" and of "Richmond Hill" at the same time.
Using "any" to optimize database filtering on collection associations
In this example, consider a case in which you are searching for people with an address in the city of Toronto. If you were to use a simple where clause such as (Person'read ... '(= (@ addrs city) "Toronto") ...), then any person with multiple Toronto addresses would be returned multiple times, once for each address match. To avoid this redundancy, you can use the "any" operator as part of a two-part association path, as follows:
(any (= (@ addrs city) "Toronto"))
Alternatively, you can use the "js" syntax:
any(addrs.city == "Toronto")
Conditions in where clause association paths
This syntax allows you to restrict parts of association paths to specific subclasses and also to apply arbitrary conditions on association paths.
A common use case for this is to limit an association based on a qualifier; for example, to look for all Entities with a user named "Shaw" acting as their advisor. The query for this case is:
(= (@ coverage (= (@ coverageRole roleName) "Advisor") userPerson lastName) "Shaw")
or with "js" syntax:
this.coverage(coverageRole.roleName=="Advisor").userPerson.lastName == "Shaw"
Note that the "this" is required in the above "js" syntax to avoid ambiguity
Subclass restrictions
In this example, homeAddress is an attribute that is only found on the Person subclass of Entity
(= (@ entity (instance? (@) Person) homeAddress city) "Toronto")
Arbitrary conditions
When applied to a read of the Telcom classes, this example returns all instances that have an entity with a lastname of "Shaw", an address in "Toronto", and a non-null value in the address2 line.
(not (null? (@ entity (= (@ lastName) "Shaw") addrs (= (@ city) "Toronto") address2)))
Reverse associations (@@)
You can apply conditions to reverse associations as well as the more typical forward associations. Reverse associations query from the end of an association "back" to the class you are reading. To compare the syntax structures:
A forward association takes the form (@ part1 part2 part3 ...) where the @ represents an instance of the class being read.
A reverse association takes the form (@@ <ClassName> part3 part2 part1) where part1 represents an attribute with a type of the class being read.
Forward associations are far more common, but you may encounter the reverse asociation syntax if you are tracing requests from NexJ UI clients.
Address?where=(= (@@ User person addrs) <userId>)
orderBy
The expressions by which to order the collection - a list of (<sort-expr> . <ascending?>) pairs.
For example:
(((@ firstName) . #t) ((@ lastName) . #f))
or more simply, since it is a single associate path length
((firstName . #t) (lastName . #f))
Or, using "js" syntax:
in the simple case if you used an expression it would sort ascending
orderBy=lastName
For more than one attribute, separate them by commas
orderBy=lastName, firstName
To indicate descending or explicitly ascending, use the following syntax:
orderBy=lastName asc, firstName desc
count
The number of instances to retrieve. Null '() for the default value of -1, meaning up to the readLimit specified in the data source. You cannot pass a larger count than the readLimit defined in the data source.
offset
The number of top level instances to skip from the beginning of the retrieved collection. Default value of 0.
$enum=value|object
This parameter determines if enumerations are returned as values or sub-objects.
The default for the recommended rest/json and rest/xml endpoints is $enum="value". The legacy endpoints /json and /xml default to $enum="object".
$enum=value
Enumerations are represented as the "value" of the enumeration as seen in the entityStatus part below. This is easier to work with than as sub-objects so is the default on our rest endpoints.
[
{
"$class": "Person",
"$oid": "100BFAA7399522406195DE457CD94C519B",
"lastName": "Lamont",
"addrs": [
{
"$class": "Address",
"$oid": "1090704C0AA3A04A539FDF1B31B10798E2",
"city": "Toronto"
},
{
"$class": "Address",
"$oid": "10C4317637F4AE432993BD488C1FC7D563",
"city": "Toronto"
}
],
"entityStatus": "Client",
"firstName": "Tim"
},
$enum=object
Enumeration values are represented as sub-objects as seen in the entityStatus part below. This is the default behavior of the legacy /json and /xml endpoints.
[
{
"$class": "Person",
"$oid": "100BFAA7399522406195DE457CD94C519B",
"lastName": "Lamont",
"addrs": [
{
"$class": "Address",
"$oid": "1090704C0AA3A04A539FDF1B31B10798E2",
"city": "Toronto"
},
{
"$class": "Address",
"$oid": "10C4317637F4AE432993BD488C1FC7D563",
"city": "Toronto"
}
],
"entityStatus": {
"$class": "EntityStatusEnum",
"$oid": "46436C69656E7442656E4D53454E54495459535441545553",
"value": "Client"
},
"firstName": "Tim"
},
HTTP Method
Maps as follows. GET=read, POST=create or invoke, PUT=update, DELETE=delete
Body
The body of the request when creating, updating, or optionally deleting is an object structure similar to that returned during a read (or GET).
JS expression reference
The following table shows some typical equivalent expressions using js and @ notation.
js expression | @ expression | Notes |
---|---|---|
a.b | (@ a b) | |
this.a | (@ a) | |
Global.XYZEnum.A | (XYZEnum'A) | |
a.b(m == n).c | (@ a b (= m n) c) | == must be escaped in the URL |
a.b(m eq n).c | (@ a b (= m n) c) | |
this.a(m eq n).b | (@ a (= m n) b) | "this" required here to avoid ambiguity |
a eq b and not (c like "xyz") | (and (= a b) (not (like? c "xyz"))) |