Jangle Specification Version 1.0

Introduction and Background to Jangle

Jangle is a specification for applying the Atom Publishing Protocol (AtomPub) to library resources and for exposing these resources simply and RESTfully.

There are three basic principles that define Jangle:

For search (which is optional), Jangle employs OpenSearch and the Atom Syndication Format for search results and CQL to provide specificity and a common vocabulary of indexes among implementations.

The metadata formats that are exposed at the various entities are defined by the communities of practice that are implementing Jangle, so Integrated Library Management Systems may or may not expose the same metadata formats as an Electronic Resources Management System or Interlibrary Loan System. Alternate metadata formats for feeds or individual resources are advertised via atom:link elements using "rel" attributes comprised of Jangle-specific URIs.

For an application to be "Jangle-compliant", it is not necessary to provide both connector and core components. Compatibility with either component is sufficient to the specification. For example, an application may only provide the connector functionality (a RESTful JSON interface that conforms to the Jangle connector API) or the developer may choose to bypass the core/connector design and apply the AtomPub interface directly to a business logic layer, as long is its behavior resembles that of the Jangle AtomPub specification.

Entities and URI structure - Specification Version 1.0

The Jangle data model is divided up into four distinct concepts, referred to as entities, that are reflected as paths in any Jangle implementation. None of the entities are required, although it is expected that some entities are dependent on others to make sense.

Actors
User accounts in a particular system. In an ILMS, this could be the borrowers. In a repository system, this could be the authors or submitters. It is not expected that actors are dependent on the existence of any other entity types.
Jangle URI: http://jangle.org/vocab/Entities#Actor
Jangle path: /actors/
Collections
A predefined set of *any* of the other entity types. Collections cannot exist without other entities (although these entities could exist in a different Jangle service).
Jangle URI: http://jangle.org/vocab/Entities#Collection
Jangle path: /collections/
Items
A specific physical representation (such a single book on a shelf), location (a URL to a digital representation or specific file format) or range of holdings (for serials). Roughly analogous to FRBR's Item Entity. It is expected that Items would require the existence of Resources.
Jangle URI: http://jangle.org/vocab/Entities#Item
Jangle path: /items/
Resources
The primary objects being exposed by a Jangle implementation. In an ILMS, this would likely be the bibliographic records.
Jangle URI: http://jangle.org/vocab/Entities/#Resource
Jangle path: /resources/

Jangle URIs follow a strict design model. In implementations using the standard Jangle core/connector architecture, URIs conform to the following syntax:

http://example.org/{service_name}/{entity}/{id}/{relationship}

which, in reality, translates to:

At the entity paths (i.e. /actors/, /collections/, /items/, /resources/), it is expected that the results will be returned descending by last modified timestamp.

Feeds returned at the relationship level (http://demo.jangle.org/openbiblio/actors/1711/items/) would return entries with identifiers at the base level:

http://demo.jangle.org/openbiblio/items/000005779

A Jangle implementation that only conforms to the AtomPub syntax can return URIs that look like:

http://example.org/{entity}/{id}/{relationship}

Ids can be sent as a list (separated by commas or semicolons) or as a range (delimited by hyphens).

Only one entity can be requested at a time (i.e. only actors or items, not a list of actors and items) and there should be no expectation of preserving order (although this is desirable if possible).

Jangle core servers *must* have an AtomPub service document at /services/ (or provide redirection from /services/ to an AtomPub service document).

Entities can be filtered via Atom categories. Categories are defined with the following URL pattern:

http://demo.jangle.org/openbiblio/resources/-/opac

The lone hyphen in the path indicates that the rest of the path are categories. This means that category filters can only be placed on the last entity requested, so if the URI contains a relationship, such as /actors/1711/items/, the filter can only be applied to items, i.e. /actors/1711/items/-/holds.

Jangle Core - Specification Version 1.0

The Jangle Core is the Atom Publishing Protocol interface to Jangle. It enables clients to access and (eventually) create and modify library resources RESTfully using regular AtomPub libraries (and conventions). The core also serves as the mediator between the client and the connector, receiving incoming requests, brokering them to the appropriate connector, parsing the JSON responses and serializing them into appropriate XML documents (Atom Syndication Format, Atom Services documents, OpenSearch Description documents, etc.).

A Jangle application does not need to follow the core/connector to be compliant, as long as the client interface responds appropriately. To be compliant as a Jangle core, however, it must be able to communicate with at least one connector.

The only HTTP methods covered by this specification are GET and HEAD. Future revisions will define how the other methods are handled.

All Jangle applications must respond with an Atom Services document at base_url/services/ (or be redirected to one) and return the content-type application/atomsvc+xml. This is the only reserved path for Jangle core compliant applications.

Typically, Jangle applications proxy a connector off of a subdirectory off of the base_url.
Example:
http://demo.jangle.org/openbiblio/

Where http://demo.jangle.org/ is the base url and an openbiblio is the path to the connector.

Core/Connector Communication - Specification Version 1.0

Jangle core communicates to connectors via HTTP. The connector responses are JSON objects.

Jangle core should submit all incoming HTTP header information to the connector (excluding accept headers). The content-type that Jangle core should request from the connector is application/json. The core is expected to also send an additional header, X-Connector-Base which is set to the URI of the Jangle core host and the path to the connector.

Example of X-Connector-Base header:
X-Connector-Base: http://demo.jangle.org/openbiblio/

Without this header, a Jangle connector cannot construct appropriate URIs, especially within the resource data.

The core should return the HTTP status codes verbatim from the connector.

Connector entity paths do not have to conform to the Jangle entity names. The paths to entities must be declared in the connector services response, however all public Jangle URIs must conform to the Jangle entity URI structure.

Service Document - Specification Version 1.0

All Jangle implementations must return an Atom Services document from the base_url/services/ URL.

Example:
http://demo.jangle.org/services/

Methods allowed: GET, HEAD

Content-type required: application/atomsvc+xml

The schema and syntax of the service document is identical to how it is defined in RFC 5023.

Here is how Jangle uses the Atom Service elements:

Workspace
This corresponds to a Jangle connector service. The Workspace title can either be the service path name or a human readable description of the service.
Collection
An entity available within a connector. The title can either be a human readable description of the data or just the entity name.
Accept
This element is currently out of scope for this revision of Jangle.
categories
Jangle does not define the use of category documents (that is, their implementation is left to the developer). Categories are defined in the connector services response. A category must be consistent across collections within a workspace. If a term is used in more than one collection, it must have the identical schema and label as used in the other collections.

An example service document:

<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
  <workspace>
    <atom:title>openbiblio</atom:title>
    <collection href="http://demo.jangle.org/openbiblio/items">
      <atom:title>Holdings records</atom:title>
    </collection>
    <collection href="http://demo.jangle.org/openbiblio/actors">
      <atom:title>Borrowers</atom:title>
    </collection>
    <collection href="http://demo.jangle.org/openbiblio/collections">
      <atom:title>Categories</atom:title>
    </collection>
    <collection href="http://demo.jangle.org/openbiblio/resources">
      <atom:title>Bibliographic records</atom:title>
      <categories fixed="no">
        <atom:category
          scheme="http://jangle.org/vocab/terms#dlf-ilsdi-resource"
          term="opac" />
      </categories>      
    </collection>
  </workspace>
  <workspace>
    <atom:title>prism</atom:title>
    <collection href="http://demo.jangle.org/prism/actors">
      <atom:title>Borrowers</atom:title>
    </collection>
    <collection href="http://demo.jangle.org/prism/collections">
      <atom:title>Categories</atom:title>
    </collection>
    <collection href="http://demo.jangle.org/prism/resources">
      <atom:title>Bibliographic records</atom:title>
    </collection>
  </workspace>  
</service>

Mapping the Services Document - Specification Version 1.0

Here is how the connector JSON elements should be applied to the Atom Service document.

The "type" element being set to "services" tells the Jangle core that it should serialize the response into an Atom Service document.

workspace/title
This should be set from the "title" element.
workspace/collection/@href
This should be set to base_url/service_path/entity/ where the entity is lowercase and plural (actors rather than Actor). Do not derive this from the "path" element in the entity object, since the connector may have non-standard paths to entity resources.
workspace/collection/title
This should come from the ["entities"][{entity}]["title"] element.
workspace/collection/categories/category/@term
Taken from ["entities"][{entity}]["categories"]
workspace/collection/categories/category/@scheme
Set to ["categories"][{term}]["scheme"] where {term} is the value of ["entities"][{entity}]["categories"]
workspace/collection/categories/category/@label
Set to ["categories"][{term}]["label"] where {term} is the value of ["entities"][{entity}]["categories"]

Feed Document - Specification Version 1.0

Feed documents are main document types used in Jangle. They are Atom Syndication Format documents and carry the resource data in the atom:entry/content element. Feed elements are used for both collections of resources and single resources.

Example:
http://demo.jangle.org/openbiblio/actors/

Methods allowed: GET, HEAD

Content-type required: application/atom+xml

The schema and syntax of the Atom Feed is structurally identical to how it is defined in RFC 4287, although Jangle defines two extension attributes that appear in <link> elements: jangle:format and jangle:relationship.

Since each entity could span many thousands to millions of resources, Jangle uses feed paging as defined in RFC 5005. The way paged feeds are retrieved and how many resources are contained in given feed is left to the discretion of the implementer.

As a general rule, Jangle feeds that contain more than one resource should be sorted descending by last modification date. Feeds of lists or ranges of resources have no set expectation on the order of resources contained.

An example feed document:

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:jangle="http://jangle.org/vocab/">
  <title>openbiblio</title>
  <link href="http://demo.jangle.org/openbiblio/resources/"/>
  <updated>2008-10-02T14:39:37Z</updated>
  <link rel="first" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/?offset=0"/>
  <link rel="last" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/?offset=6000"/>
  <link rel="next" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/?offset=100"/>
  <link rel="self" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/" jangle:format="http://jangle.org/vocab/formats#http://www.loc.gov/MARC21/slim"/>
  <link rel="http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/?format=dc"/>
  <link rel="http://jangle.org/vocab/formats#application/marc" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/?format=marc"/>
  <link rel="http://jangle.org/vocab/formats#http://www.openarchives.org/OAI/2.0/oai_dc/" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/?format=oai_dc"/>
  <link rel="http://jangle.org/vocab/formats#http://www.loc.gov/mods/v3" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/?format=mods"/>  
 <link rel="search"
         href="http://demo.jangle.org/openbiblio/resources/search/description/" 
         type="application/opensearchdescription+xml" />
  <id>http://demo.jangle.org/openbiblio/resources/</id>
  <entry>
    <title>The Untamed</title>
    <link href="http://demo.jangle.org/openbiblio/resources/5878" jangle:format="http://jangle.org/vocab/formats#http://www.loc.gov/MARC21/slim"/>
    <link rel="related" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/5878/collections/" jangle:relationship="http://jangle.org/vocab/Entity#Collection"/>
    <link rel="alternate" type="text/html" href="http://catalog.jangle.org/openbiblio/shared/biblio_view.php?bibid=5878&tab=opac"/>
    <link rel="http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/5878?format=dc"/>
    <link rel="http://jangle.org/vocab/formats#application/marc" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/5878?format=marc"/>
    <link rel="http://jangle.org/vocab/formats#http://www.openarchives.org/OAI/2.0/oai_dc/" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/5878?format=oai_dc"/>
    <link rel="http://jangle.org/vocab/formats#http://www.loc.gov/mods/v3" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/5878?format=mods"/>
    <author>
      <name>Brand, Max,</name>
    </author>
    <id>http://demo.jangle.org/openbiblio/resources/5878</id>
    <updated>2008-03-18T15:57:00-04:00</updated>
    <content type="application/xml">
<record xmlns='http://www.loc.gov/MARC21/slim'><leader>      Z   22        4500</leader><datafield tag='245' ind1='0' ind2='0'><subfield code='a'>The Untamed</subfield><subfield code='b'></subfield><subfield code='c'>by Max Brand</subfield></datafield><datafield tag='100' ind1='0' ind2='z'><subfield code='a'>Brand, Max,</subfield></datafield><datafield tag='42' ind1='z' ind2='z'><subfield code='a'>dc</subfield></datafield><datafield tag='100' ind1='z' ind2='z'><subfield code='d'>1892-1944</subfield></datafield><datafield tag='245' ind1='z' ind2='z'><subfield code='h'>[electronic resource] /</subfield></datafield><datafield tag='260' ind1='z' ind2='z'><subfield code='b'>Project Gutenberg,</subfield><subfield code='c'>2004</subfield></datafield><datafield tag='500' ind1='z' ind2='z'><subfield code='a'>Project Gutenberg</subfield></datafield><datafield tag='506' ind1='z' ind2='z'><subfield code='a'>Freely available.</subfield></datafield><datafield tag='516' ind1='z' ind2='z'><subfield code='a'>Electronic text</subfield></datafield><datafield tag='830' ind1='z' ind2='z'><subfield code='a'>Project Gutenberg</subfield><subfield code='v'>10886</subfield></datafield><datafield tag='856' ind1='z' ind2='z'><subfield code='u'>http://www.gutenberg.org/etext/10886</subfield><subfield code='3'>Rights</subfield></datafield><datafield tag='856' ind1='z' ind2='z'><subfield code='u'>http://www.gutenberg.org/license</subfield></datafield></record>    </content>
    <category term="opac"/>
  </entry>
  ...
</feed>

Jangle has defined special atom link "rel" attribute values to define alternate data formats available for the resources in a given feed and the related resources to any given atom:entry.

To declare a feed for the same set of resources (or resource) using a different record format (in this case, Dublin Core rather than marcxml), the following syntax is used:

<link rel="http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/" type="application/atom+xml" href="http://demo.jangle.org/openbiblio/resources/5878?format=dc"/>

The rel attribute uses a URI that unambiguously declares the format that would be delivered in the linked feed. The URI http://jangle.org/vocab/formats# must be present to use this link syntax. The remaining URI fragment (in this case, http://purl.org/dc/elements/1.1/) is an identifier to denote the data format present. It does not have to be a namespace URI (any string will do). The full URI (http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/) should be dereferenceable to a definition of the identifier. It is within the scope of the Jangle initiative to provide this vocabulary (and a means to contribute to it).

The type attribute must be application/atom+xml. The href attribute should be the URI to the feed which contains the alternate data format. The URI pattern is left to the discretion of the implementer.

These link elements may appear as child elements of the <feed> element or an <entry> element.

Jangle feeds also define relationships between resources. If an Actor has multiple Items associated with it (for example, a borrower has several items checked out), that relationship can be declared like this:

<link type="application/atom+xml" href="http://demo.jangle.org/openbiblio/actors/1711/items/" rel="related" jangle:relationship="http://jangle.org/vocab/Entity#Item"/>

Jangle defined an extension attribute, jangle:relationship to provide the connection between two (or more) resources.

In order to explicitly identify the format of the entity content element payload, Jangle has defined another extension attribute: jangle:format. For feeds with homogeneous record formats (i.e. all entries use the same format), this extension should appear on the rel="self" link element at the feed level and on a link element in the entry. If the record formats are heterogeneous within the feed, the attribute need only appear on link elements within the entry elements. The value of the attribute should be a Jangle format URI (such as: http://jangle.org/vocab/formats#application/marc).

If an entity is flagged as searchable in the connector services response, the Jangle core should also provide an OpenSearch autodiscovery link:
<link rel="search"
href="http://demo.jangle.org/openbiblio/resources/search/description/"
type="application/opensearchdescription+xml" />

Which would actually return a Jangle Explain document.

Mapping the Feed Document - Specification Version 1.0

Here is how the connector JSON elements should be applied to the Atom Syndication Format document.

The "type" element being set to "feed" tells the Jangle core that it should serialize the response into an Atom Syndication Format document.

See an example feed for a point of reference.

feed/title
This is not strictly defined, but preferred practice would be to set it from the service_name/entity or in the case of a single resource service_name/entity/entity_title: openbiblio/resources, openbiblio/actors/Kucinich, Dennis
feed/updated
The "time" element or "Last-Modified" HTTP header from the connector.
id
The request URI of the feed. This can come from the "request" element.
feed/link/@self
This, like the <id> element, is the request URI. This element should include a jangle:format element on feeds with homogeneous record formats. The value of this attribute should be the only item in the "formats" element.
feed/link/@next
Drawn from the paging model. If the ["totalResults"] is a greater number than the sum of the ["offset"] element and the number of objects in the ["data"] array, this needs to have a value.
feed/link/@previous
Needs to be set if ["offset"] is anything but 0.
feed/link/@last
The feed of the last page of resources, if paging is needed.
feed/entry/id
The resource URI, taken from ["data"][i]["id"]
feed/entry/link (no "rel" attribute or rel="self")
The href attribute should be populated with the ["data"][i]["id"] element. Each entry must have link element that contains a jangle:format attribute that explicitly defines the record type contained in the content element. This attribute's value can be derived from ["data"][i]["format"].
feed/entry/title
Taken from ["data"][i]["title"]
feed/entry/updated
Supplied by ["data"][i]["updated"]
feed/entry/created
From ["data"][i]["created"]
feed/entry/author/name
If ["data"][i]["author"] exists, use that, otherwise, set to "n/a"
feed/entry/content
The data contained in ["data"][i]["content"]
feed/entry/content/@type
The value of ["data"][i]["content_type"]
feed/entry/summary
If it exists, use ["data"][i]["description"]
feed/entry/category/@term
Provided by ["data"][i]["categories"]

To build the <link> elements, it is probably easiest to look at this from the opposite direction.

The link elements would be built from the "alternate_formats", ["data"][i]["alternate_formats"], ["data"][i]["relationships"], "links", ["data"][i]["links"] elements.

From the "alternate_formats" and ["data"][i]["alternate_formats"] elements, the <link> element's attributes would come from:

rel
This is special Jangle URI. The value is the key of the "alternate_formats" hash.
href
The value is the value of the "alternate_formats" hash.
type
This should always be application/atom+xml

The ["data"][i]["relationships"] are used the following way:

rel
This uses the standard rel="related" relationship.
href
The value is the value of the "relationships" hash.
type
This should always be application/atom+xml.
jangle:relationship
The value is set from the key of the "relationship" hash. This is an extension to Atom.

The <link> tags created from the "link" elements is pretty straightforward. The "rel" attribute value is set from the hash key ("alternate", "relation", etc.). The remaining object properties should be set as the element attributes ("href", "type", "title", etc.).

Stylesheets - Specification Version 1.0

Jangle connector feed or search responses can include URLs to XSLT stylesheets to be applied at either the "feed" level or the "entry/content" level. Jangle does not define which version of XSLT is permissible, but developers should not assume all Jangle core implementations can support XSLT 2.0.

The stylesheet URLs must be accessible to the Jangle core via HTTP.

"Feed" level stylesheets (which is sent in JSON as an array) are to be applied after the connector response has been serialized as an Atom Syndication Format feed. The stylesheets are applied serially, in the order they appear in the array.

Resource level stylesheets can be applied at any time, since they should transform the data in the atom:entry/content tag as an independent document, not as a child element in the Atom feed.

The stylesheets should not transform the Atom into another format, although they can be used to set the values of the Atom elements or provide Atom extensions.

Searching - Specification Version 1.0

Searching in Jangle is provided through OpenSearch. The query syntax uses CQL to define search indexes.

Jangle extends the OpenSearch Description document to include elements of the SRU explain document schema.

Search results use a similar Atom document to the feed results, but include the OpenSearch extensions.

Search Description Document - Specification Version 1.0

The description document advertises how to search resources for clients.

Example:
http://demo.jangle.org/openbiblio/resources/search/description/

Methods allowed: GET, HEAD

Content-type required: application/opensearchdescription+xml

Jangle extends the standard OpenSearch Description document with elements from the SRU Explain schema. This allows clients to know which indexes are available to search on in the query element.

Example Jangle Search Description Document.

<?xml version="1.0" encoding="utf-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:jangle="http://jangle.org/opensearch/">
  <ShortName>Bibliographic records</ShortName>
  <LongName>Search Bibliographic records in OpenBiblio</LongName>
  <Description>Bibliographic records search.  Defaults to keyword anywhere.</Description>
  <Tags>catalog library</Tags>
  <SyndicationRight>open</SyndicationRight>
  <Url type="application/atom+xml" template="http://demo.jangle.org/openbiblio/resources/search/?offset={startIndex?}&count={count?}&query={searchTerms?}&format={jangle:format?}"/>
  <Query role="example" searchTerms="dc.creator=thomas">
<zr:explain xmlns:zr="http://explain.z3950.org/dtd/2.1/">
  <zr:indexInfo>
    <zr:set name="dc" identifier="info:srw/cql-context-set/1/dc-v1.1"/>
    <zr:set name="rec" identifier="info:srw/cql-context-set/2/rec-1.1"/>    
    <zr:set name="cql" identifier="info:srw/cql-context-set/1/cql-v1.2"/>        
     <zr:index>
       <zr:map><zr:name set="dc">title</zr:name></zr:map>
     </zr:index>
     <zr:index>
       <zr:map><zr:name set="dc">creator</zr:name></zr:map>
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="dc">subject</zr:name></zr:map>
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="dc">publisher</zr:name></zr:map>
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="dc">format</zr:name></zr:map>
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="dc">identifier</zr:name></zr:map>
     </zr:index> 
     <zr:index>
       <zr:map><zr:name set="rec">identifier</zr:name></zr:map>       
     </zr:index>         
     <zr:index>
       <zr:map><zr:name set="rec">collectionName</zr:name></zr:map>       
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="rec">lastModificationDate</zr:name></zr:map>       
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="rec">creationDate</zr:name></zr:map>       
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="cql">allRecords</zr:name></zr:map>       
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="cql">allIndexes</zr:name></zr:map>       
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="cql">anyIndexes</zr:name></zr:map>       
     </zr:index>     
     <zr:index>
       <zr:map><zr:name set="cql">keywords</zr:name></zr:map>       
     </zr:index>     
  </zr:indexInfo>
</zr:explain>
      <Query>
  
</OpenSearchDescription>

The "explain" data becomes a child element of the OpenSearch:Query element.

Search Results Feeds - Specification Version 1.0

Jangle search results are mostly identical to standard Entity feeds with the notable addition of the OpenSearch extension.

Search feeds can be identified from the Jangle connector by the "type" property being set to "search".

In addition to the mapping of the JSON properties to the feed document, the following properties should be mapped to OpenSearch elements:

"totalResults"
This should be mapped to the value of the <opensearch:totalResults> element.
"offset"
Sets the <opensearch:startIndex> element's value.

The <opensearch:itemsPerPage> element can be set to the number of objects in the "data" array. Note: there is an assumption that Jangle feeds provide a consistent number of resources per page. As a result, provisions may need to be made for the last page in a result set in the event that the number of resources in the feed is less than the normal page default.

The search query terms (which should be a CQL query string) should be used to make the OpenSearch Query element:

<opensearch:Query role="request" searchTerms="{cql_query_string}" startIndex="0" />

Since feed offsets start with "0", search startIndex attributes should also be set to "0".

Search results feeds must include the OpenSearch namespace declaration: http://a9.com/-/spec/opensearch/1.1/

Connector API - Specification Version 1.0

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:

  1. Services: An explanatory response of the entities available in the connector, the paths to those entities, whether or not an entity is searchable, what kinds of mime-types are accepted in POST and PUT operations and what category filters can be used with a particular entity.
  2. Feed: A paged list of resources. Can include the relationships between resources and links to alternate metadata formats available.
  3. Explain: Analogous to an OpenSearch Description Document but also contains elements found in an SRU Explain response.
  4. Search: Basically identical to a feed response, a search response allows a Jangle core to return the results using OpenSearch extension elements.

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/.

Connector HTTP Status Codes -Specification Version 1.0

HTTP Status Codes

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 Services Response - Specification Version 1.0

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:

type:
The type of API response. This must be "services".
version:
The version of the API response. This must be "1.0".
title:
The name of the service. Is used by the Jangle core to set the URI paths. Cannot contain spaces or any non-alphanumeric characters.
request:
The request URI echoed to the request client.
entities:
A hash of the entities available for requesting from this server. (possible values: Actors, Collections, Items, Resources)
categories:
A hash of the filters that can be applied to entities.

And the definitions of the fields for the entities are:

title:
A human readable string for the contents of the entity.
path:
The base path (from the connector base url) to the entity.
searchable:
The URI of the entity's explain response or a boolean false if search is not supported.
categories:
An array of the category label that can be used with this entity.

And the categories are defined like this:

term:
The string used to identify the category. Note these must be unique across a Jangle connector service. In the JSON output this is the key of the categories hash.
scheme:
This is optional, but could have the value of a URI that explicitly defines what the category is (i.e. http://jangle.org/vocab/terms#recall)
label:
An optional human readable string describing the category.

Connector Feed Response - Specification Version 1.0

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:

type:
Sets the feed type to allow the Jangle core to set the proper serialization of the JSON response. This can be either "feed" or "search" depending on the request type.
request:
The URI of the requested resource (including query parameters).
time:
An ISO 8601 formatted timestamp denoting when the response was created.
offset:
The offset (from zero) of the first entry in the result set.
totalResults:
The number of resources available at this URI.
extensions:
A hash of namespaces of any extension data being send in the results. The syntax is "extensions": {"xmlns prefix:{"xmlns":"Namespace URI","xsd":"Schema URL (optional)"}}
alternate_formats:
A hash of the URIs of feeds that represent the same resources in a different data format. Takes the form: "alternate_formats":{"Jangle URI of format (i.e. http://jangle.org/vocab/formats#http://purl.org/dc/elements/1.1/)":"URI of resource with alternate format"}
stylesheets:
An optional array of XSLT stylesheet URLs to transform the data after it has been serialized as Atom. The stylesheets will be applied in the order of the array.
categories:
An optional array of category terms that apply to the resource.
formats:
An array of Jangle Format URIs that appear in the current list of resources.
data:
An array of objects that contain the actual resources requested.

The objects in the data array have the following elements:

id:
The URI of the resource.
title:
A human readable string to identify the resource.
updated:
An ISO 8601 formatted timestamp of the last modification time of the resource.
created:
An optional ISO 8601 formatted timestamp of the creation date of the resource.
description:
A human readable description of the resource. Optional.
content:
This element contains the actual resource data itself in the format requested by the client. While technically "optional", this element should only be omitted if the client explicitly requests the results in Atom. The content element requires the presence of content_type in the same object.
content_type:
The mime-type of the content element. Required if the content element exists.
format:
The Jangle Format URI of the data in the "content" element.
stylesheet:
The URL of an XSLT stylesheet to apply to the data contained in the content element. Optional.
alternate_formats:
A hash of the URIs of feeds that represent the same (single) resource in a different data format. Same syntax as the root level element.
relationships:
A hash of feeds of resources related this resource. Takes the form: "relationships":{"Jangle URI of related entity (i.e. http://jangle.org/vocab/Entity#Item)":"URI to resource"}
categories:
An optional array of category terms that apply to the resource.
author:
An optional string identifying the creator of the resource.
links:
A representation of the Atom link element. These do not need to include alternate_formats and relationships links.

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.

Connector Explain Response - Specification Version 1.0

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:

type:
Sets the type of the feed for the Jangle core to serialize properly. Must be explain
request:
The URI of the explain document.
shortname:
A brief label for the search. Optional. If not set, Jangle will use the entity name.
description:
A human readable description of the search target.
template:
A URL template conforming to OpenSearch URL parameters syntax.
contact:
An email address of the maintainer of the connector search. Optional
tags:
An optional array of terms to classify the search target.
longname:
An optional, human-readable string to identify the search target.
image:
An optional hash to provide an icon or image for the search target.
The elements of the hash are:
height:
The height, in pixels, of the image. Optional.
width:
The width, in pixels, of the image. Optional.
type:
The mime-type of the image. Optional.
location:
The URL of the image.
query:
A hash that sets the query definitions. Optional.
The elements of the hash are:
example:
An optional example query string that will return results for this search target.
context-sets:
An array of objects declaring the indexes available to search. Optional.
The elements of this object are:
name:
The prefix used in queries to declare the context set to use.
identifier:
The URI of the context set.
indexes:
An array of indexes available to use within the context set.
developer:
A human readable string of the maintainer or creator of the application. Optional
attribution:
An optional string containing any sources that should be credited for the content.
syndicationright:
An enumeration of redistribution privileges. Options are open, limited, private or closed. This element is optional (although omission defaults to "public".
adultcontent:
Boolean. Optional. Omission assumes "false".
language:
An optional string or array of language codes.
inputencoding:
Optional. Defines the character encoding of the query string. Default is UTF-8.
outputencoding:
Optional. Defines the character encoding of the query string. Default is UTF-8. Relevant for MARC-8 encoded data.

Connector Search Response - Specification Version 1.0

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).