The Jangle connector is an application that translates the back-end business logic, data models, relationships and workflows into the Jangle entities. In version 1.0, connectors must be REST based and follow the URI structure of standard Jangle resources (although the resources themselves can have a different name locally, see the section on connector service responses for more information). All responses are a combination of HTTP status codes and JSON data structures.
There are four kinds of responses a Jangle connector may return:
All Jangle connectors must return a services response at {base_url}/services/.
Jangle connectors should not define absolute URLs internally, since it is possible that the same connector could be used by multiple Jangle cores (which, in turn, would mint different URIs for the same resource). Instead, connectors should be written to expect an HTTP Request header, $HTTP_X_CONNECTOR_BASE, to be sent by the Jangle core, and use that to construct absolute URIs. In the absence of this header, it is permissible for a connector to send a relative URI.
While connector paths do not have to conform to Jangle entity names, the URIs they create do, so if "actors" is mapped to /borrowers/ and "items" is mapped to /holdings/, URIs would still need to be returned as /actors/{id}/items/ and not /borrowers/{id}/holdings/.
Note, the only status codes defined here are in relation to GET requests at specific resources. Since version 1.0 of the spec only defines GET, it is out of scope of this document to define responses to other HTTP methods.
For a successful request that returns a valid Jangle JSON response, return a 200 (OK).
Return a 301 (Moved Permanently) or 302 (Found) for requests that need to be redirected to the appropriate URI. Examples would be redirecting a request for /resources to /resources/ or if identifiers changed (say after an ILMS migration, for example) /resources/1234 to /resources/9876.
Connectors can utilize the 304 (Not Modified)/etags response for any request, although Jangle core servers may not be able to use it (depending on whether or not they maintain caches).
Authentication/Authorization (401 and 403 responses) is at the discretion of the implementer. They are legal on *any* URI.
For any invalid path or id, return a 404 (Not Found) response.
For a URI that was once valid but is no longer (whether via deletion or deactivation of some sort), it is acceptable to send either a 410 (Gone) or a 404 (Not Found).
For version 1.0 of this spec, clients should expect to receive a 405 (Method Not Allowed) by sending any HTTP method besides GET to a resource. However, it is possible and acceptable for implementers to create their own methods for handling create, update and delete requests, so this is not mandatory.
Connector service responses must resolve at base_url/services/ (or be redirected to from there).
Example:
http://connector.jangle.org/services/
Methods allowed: GET, HEAD
Content-type required: application/json
JSON Schema:
{
"description":"Services available from this Jangle connector",
"type":"object",
"properties":{
"type":{"type":"string","pattern":"/^services$/"},
"version":{"type":"string","pattern":"/^1\.0$/"},
"title":{"type":"string",},
"request":{"type":"string"},
"entities":{"type":"object",
"properties:{
"Actor:{"optional":true,"type":"object",
"properties":{
"title":{"type":"string"},
"searchable":{"type":["boolean","string"],"format":"uri"},
"path":{"type":"string"},
"categories":{"optional":true,"type":"array"}
},
"Collection:{"optional":true,"type":"object",
"properties":{
"title":{"type":"string"},
"searchable":{"type":["boolean","string"],"format":"uri"},
"path":{"type":"string"},
"categories":{"optional":true,"type":"array"}
},
"Item:{"optional":true,"type":"object",
"properties":{
"title":{"type":"string"},
"searchable":{"type":["boolean","string"],"format":"uri"},
"path":{"type":"string"},
"categories":{"optional":true,"type":"array"}
},
"Resource:{"optional":true,"type":"object",
"properties":{
"title":{"type":"string"},
"searchable":{"type":["boolean","string"],"format":"uri"},
"path":{"type":"string"},
"categories":{"optional":true,"type":"array"}
}
},
"categories":{"optional":true,"type":"object"}
}
Which, in real terms, would return a response that looks something like:
{
"title": "openbiblio",
"version" "1.0",
"type": "services",
"entities":
{
"Resource":
{
"searchable": "/resources/search/description",
"title": "Bibliographic records",
"path": "/resources/",
"categories": ["opac"]
},
"Collection":
{
"searchable": false,
"title": "Categories",
"path": "/collections/"
},
"Item":
{
"searchable": false,
"title": "Holdings records",
"path": "/items/"
},
"Actor":
{
"searchable": false,
"title": "Borrowers",
"path": "/actors/"
}
},
"request": "/services/",
"categories":{
"opac":{
"scheme": "http://jangle.org/vocab/terms#dlf-ilsdi-resource",
"label": "Resources that are available for harvesting in a discovery interface"
}
}
}
The definitions of each field are:
And the definitions of the fields for the entities are:
And the categories are defined like this:
Feed responses are returned from any of the entity paths, including requests for single resources and relationships.
Example:
http://connector.jangle.org/resources/
Methods allowed: GET, HEAD
Content-type required: application/json
JSON schema:
{
"description":"A feed of entity resources",
"type":"object",
"properties":{
"type":{"type":"string","pattern":"/^feed|search$/"},
"request":{"type":"string","format":"uri"},
"time":{"type":""string","format":"date-time"},
"offset":{"type":"integer"},
"totalResults":{"type":"integer"},
"extensions":{"type":"object","optional":true,"additionalProperties":{
"type":"string","format":"uri"
}
},
"formats":{"type":"array","items":{"type":"string","format":"uri"}},
"alternate_formats":{"type":"object","optional":true,
"additionalProperties":{
"type":"string","format":"uri"
}
},
"stylesheets":{"type":"array","optional":true,"items":{"type":"string","format":"uri"}},
"categories":{"type":"array","optional":true,"items":{"type":"string"}},
"data":{"type":"array","items":[
"type":"object",
"properties":{
"id":{"type":"string","format":"uri"},
"title":{"type":"string"},
"updated":{"type":string","format":"date-time"},
"created":{"type":string","format":"date-time","optional":true},
"description":{"type":"string","optional":true},
"content":{"type":"string","optional":true,"requires":["content_type","format"]},
"content_type":{"type":"string","requires":["content","format"],"optional":true},
"stylesheet":{"type":"string","optional":true,"format":"uri"},
"format":{"type":"string","format":"uri","requires":["content","content_type"]},
"alternate_formats:{"type":"object","optional":true,
"additionalProperties":{
"type":"string","format":"uri"
}
},
"relationships":{"type":"object","optional":true,
"additionalProperties":{
"type":"string","format":"uri"
}
},
"categories":{"type":"array","optional":true,"items":{"type":"string"}},
"author":{"type":"string","optional":true},
"links":{"type":"object","optional":true,"properties":{
"alternate":{"type":"object","optional":true,"properties":{
"type":{"type":"string"},
"title":{"type":"string","optional":true},
"href":{"type":"string","format":"uri"}
}
},
"related":{"type":"object","optional":true,"properties":{
"type":{"type":"string"},
"title":{"type":"string","optional":true},
"href":{"type":"string","format":"uri"}
}
}
},
"additionalProperties":{"type":"object"}
},
"additionalProperties":{"type":"any"}
}
}
An actual JSON feed response returning MARCXML records:
{
"type": "feed",
"totalResults": 6077,
"time": "2008-09-30T16:11:03-04:00",
"offset": 0,
"request": "http://demo.jangle.org/openbiblio/resources/",
"alternate_formats":
{
"http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/": "http://demo.jangle.org/openbiblio/resources/?format=dc",
"http://jangle.org/vocab/formats#http://www.loc.gov/mods/v3": "http://demo.jangle.org/openbiblio/resources/?format=mods",
"http://jangle.org/vocab/formats#http://www.openarchives.org/OAI/2.0/oai_dc/": "http://demo.jangle.org/openbiblio/resources/?format=oai_dc",
"http://jangle.org/vocab/formats#application/marc21": "http://demo.jangle.org/openbiblio/resources/?format=marc"
},
"formats":["http://jangle.org/vocab/formats#http://www.loc.gov/MARC21/slim"],
"data":
[
{
"alternate_formats":
{
"http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/": "http://demo.jangle.org/openbiblio/resources/5878?format=dc",
"http://jangle.org/vocab/formats#http://www.loc.gov/mods/v3": "http://demo.jangle.org/openbiblio/resources/5878?format=mods",
"http://jangle.org/vocab/formats#http://www.openarchives.org/OAI/2.0/oai_dc/": "http://demo.jangle.org/openbiblio/resources/5878?format=oai_dc",
"http://jangle.org/vocab/formats#application/marc21": "http://demo.jangle.org/openbiblio/resources/5878?format=marc"
},
"links":
{
"alternate":
[
{
"type": "text/html",
"title": "Link to native interface",
"href": "http://catalog.jangle.org/openbiblio/shared/biblio_view.php?bibid=5878\u0026tab=opac"
}
],
"related":
[
{
"type": "application/atom+xml",
"href": "http://demo.jangle.org/openbiblio/resources/5878/collections/"
}
]
},
"title": "The Untamed",
"author": "Brand, Max,",
"updated": "2008-03-18T15:57:00-04:00",
"categories":
[
"opac"
],
"content": "\u003Crecord xmlns='http://www.loc.gov/MARC21/slim'\u003E\u003Cleader\u003E Z 22 4500\u003C/leader\u003E\u003Cdatafield tag='245' ind1='0' ind2='0'\u003E\u003Csubfield code='a'\u003EThe Untamed\u003C/subfield\u003E\u003Csubfield code='b'\u003E\u003C/subfield\u003E\u003Csubfield code='c'\u003Eby Max Brand\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='100' ind1='0' ind2='z'\u003E\u003Csubfield code='a'\u003EBrand, Max\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='42' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003Edc\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='100' ind1='z' ind2='z'\u003E\u003Csubfield code='d'\u003E1892-1944\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='245' ind1='z' ind2='z'\u003E\u003Csubfield code='h'\u003E[electronic resource]/\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='260' ind1='z' ind2='z'\u003E\u003Csubfield code='b'\u003EProject Gutenberg,\u003C/subfield\u003E\u003Csubfield code='c'\u003E2004\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='500' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003EProject Gutenberg\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='506' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003EFreely available.\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='516' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003EElectronic text\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='830' ind1='z' ind2='z'\u003E\u003Csubfield code='a'\u003EProject Gutenberg\u003C/subfield\u003E\u003Csubfield code='v'\u003E10886\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='856' ind1='z' ind2='z'\u003E\u003Csubfield code='u'\u003Ehttp://www.gutenberg.org/etext/10886\u003C/subfield\u003E\u003Csubfield code='3'\u003ERights\u003C/subfield\u003E\u003C/datafield\u003E\u003Cdatafield tag='856' ind1='z' ind2='z'\u003E\u003Csubfield code='u'\u003Ehttp://www.gutenberg.org/license\u003C/subfield\u003E\u003C/datafield\u003E\u003C/record\u003E",
"content_type": "application/xml",
"format":"http://jangle.org/vocab#http://www.loc.gov/MARC21/slim",
"created": "2008-03-18T15:57:00-04:00",
"id": "http://demo.jangle.org/openbiblio/resources/5878",
"relationships":
{
"http://jangle.org/rel/related#Collection": "http://demo.jangle.org/openbiblio/resources/5878/collections/"
}
}
]
}
The definitions of each field are:
The objects in the data array have the following elements:
Additional elements can be added to data objects, their keys need to conform to a convention of xmlns_prefix:element_name. Object syntax of data contained in an extension element has not yet been decided.
Explain responses are means to advertise the search attributes of a given entity. At their simplest, they provide enough information to create an OpenSearch Description Document, but they can also include the supported CQL indexes, relations and modifiers available for a search client to perform more sophisticated queries.
Example:
http://connector.jangle.org/resources/search/description/
Methods allowed: GET, HEAD
Content-type required: application/json
JSON Schema:
{
"description":"A Jangle Explain Response",
"type":"object",
"properties":{
"type":{"type":"string","pattern":"/^explain$/"},
"request":{"type":"string","format":"uri"},
"shortname":{"type":"string","optional":true,"maxLength":16},
"description":{"type":"string","maxLength":1024},
"template":{"type":"string","format":"uri"},
"contact":{"type":"string","format":"email","optional":true},
"tags":{"type":"array","items":{"type":"string"},"optional":true},
"longname":{"type":"string","optional":true,"maxLength":48},
"image":{"type":"object","optional":true,"properties":{
"height":{"type":"integer","minimum":0,"optional":true},
"width":{"type":"integer","minimum":0,"optional":true},
"type":{"type":"string","optional":true},
"location":{"type":"string","format":"url"}}
},
"query":{"type":"object","optional":true,"properties":{
"example":{"type":"string","optional":true},
"context-sets",{"type":"array","items":{"type":"object","optional":true,"properties":{
"identifier":{"type":"string","format":"uri"},
"name":{"type":"string"},
"indexes":{type":"array","items":{"type":"string"}}
}
}
},
"developer":{"type":"string","optional":true,"maxLength":64},
"attribution":{"type":"string","optional":true,"maxLength":256},
"syndicationright":{"type":"string","enum":
["open","limited","private","closed"],"optional":true},
"adultcontent":{"type":"boolean","optional":true},
"language":{"type":["string","array"],"items":{"type":"string"},"optional":true},
"inputencoding":{"type":["string","array"],"items":{"type":"string"},"optional":true},
"outputencoding":{"type":["string","array"],"items":{"type":"string"},"optional":true}
}
}
An example JSON response:
{
"type":"explain",
"shortname":"Bibliographic records",
"longname":"Search Bibliographic records in OpenBiblio",
"request":"http:\/\/connector.jangle.org\/resources\/search\/description\/",
"description":"Bibliographic records search. Defaults to keyword anywhere.",
"template":
"http:\/\/connector.jangle.org\/resources\/search\/description\/?offset={startIndex?}&
count={count?}&query={searchTerms?}&format={jangle:format?}",
"tags": ["catalog", "library"],
"syndicationright":"open",
"query":{
"example":"dc.creator=thomas",
"context-sets":[
{
"name":"dc",
"identifier":"info:srw/cql-context-set/1/dc-v1.1",
"indexes":["title","creator","subject","publisher","format","identifier"]
},
{
"name":"rec",
"identifier":"info:srw/cql-context-set/2/rec-1.1",
"indexes":["identifier","collectionName","lastModificationDate","creationDate"]
},
{
"name":"cql",
"identifier":"info:srw/cql-context-set/1/cql-v1.2",
"indexes":["allRecords","allIndexes","anyIndexes","keywords"]
}
],
},
}
}
The definitions of each field are:
Search JSON responses follow the same syntax as feed responses, the only difference being the "type" element, which should be "search".
Example:
http://connector.jangle.org/resources/search/?offset=100&count=100&query=rec.lastModificationDate>=2008-08-08
Methods allowed: GET, HEAD
Content-type required: application/json
All searchable resources must be declared as searchable in the services response and have a corresponding explain response. Connectors do not need to explicitly send a link element advertising the URI of the explain resource, since the Jangle core can glean this from the services resource.
The query terms must be in CQL syntax. If no indexes or relations are sent, the search should be translated to "cql.serverChoice all {query_terms}" (i.e. indexes of the implementor's choice, boolean ANDed). It is acceptable for a search service to support only this (i.e. no defined indexes, relations or modifiers).