Metadata

This section details the various forms of metadata provided by a Spring Data REST-based application.

Application-Level Profile Semantics (ALPS)

ALPS is a data format for defining simple descriptions of application-level semantics, similar in complexity to HTML microformats. An ALPS document can be used as a profile to explain the application semantics of a document with an application-agnostic media type (such as HTML, HAL, Collection+JSON, Siren, etc.). This increases the reusability of profile documents across media types.
— M. Admundsen / L. Richardson / M. Foster
https://tools.ietf.org/html/draft-amundsen-richardson-foster-alps-00

Spring Data REST provides an ALPS document for every exported repository. It contains information about both the RESTful transitions and the attributes of each repository.

At the root of a Spring Data REST app is a profile link. Assuming you had an app with both persons and related addresses, the root document would be as follows:

{
  "_links" : {
    "persons" : {
      "href" : "http://localhost:8080/persons"
    },
    "addresses" : {
      "href" : "http://localhost:8080/addresses"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile"
    }
  }
}

A profile link, as defined in RFC 6906, is a place to include application-level details. The ALPS draft spec is meant to define a particular profile format, which we explore later in this section.

If you navigate into the profile link at localhost:8080/profile, you see content resembling the following:

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/profile"
    },
    "persons" : {
      "href" : "http://localhost:8080/profile/persons"
    },
    "addresses" : {
      "href" : "http://localhost:8080/profile/addresses"
    }
  }
}
At the root level, profile is a single link and cannot serve up more than one application profile. That is why you must navigate to /profile to find a link for each resource’s metadata.

If you navigate to /profile/persons and look at the profile data for a Person resource, you see content resembling the following example:

{
  "version" : "1.0",
  "descriptors" : [ {
    "id" : "person-representation", (1)
    "descriptors" : [ {
      "name" : "firstName",
      "type" : "SEMANTIC"
    }, {
      "name" : "lastName",
      "type" : "SEMANTIC"
    }, {
      "name" : "id",
      "type" : "SEMANTIC"
    }, {
      "name" : "address",
      "type" : "SAFE",
      "rt" : "http://localhost:8080/profile/addresses#address"
    } ]
  }, {
    "id" : "create-persons", (2)
    "name" : "persons", (3)
    "type" : "UNSAFE", (4)
    "rt" : "#person-representation" (5)
  }, {
    "id" : "get-persons",
    "name" : "persons",
    "type" : "SAFE",
    "rt" : "#person-representation"
  }, {
    "id" : "delete-person",
    "name" : "person",
    "type" : "IDEMPOTENT",
    "rt" : "#person-representation"
  }, {
    "id" : "patch-person",
    "name" : "person",
    "type" : "UNSAFE",
    "rt" : "#person-representation"
  }, {
    "id" : "update-person",
    "name" : "person",
    "type" : "IDEMPOTENT",
    "rt" : "#person-representation"
  }, {
    "id" : "get-person",
    "name" : "person",
    "type" : "SAFE",
    "rt" : "#person-representation"
  } ]
}
1 A detailed listing of the attributes of a Person resource, identified as #person-representation, lists the names of the attributes.
2 The supported operations. This one indicates how to create a new Person.
3 The name is persons, which indicates (because it is plural) that a POST should be applied to the whole collection, not a single person.
4 The type is UNSAFE, because this operation can alter the state of the system.
5 The rt is #person-representation, which indicates that returned resource type will be Person resource.
This JSON document has a media type of application/alps+json. This is different from the previous JSON document, which had a media type of application/hal+json. These formats are different and governed by different specs.

You can also find a profile link in the collection of _links when you examine a collection resource, as the following example shows:

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/persons" (1)
    },
    ... other links ...
    "profile" : {
      "href" : "http://localhost:8080/profile/persons" (2)
    }
  },
  ...
}
1 This HAL document respresents the Person collection.
2 It has a profile link to the same URI for metadata.

Again, by default, the profile link serves up ALPS. However, if you use an Accept header, it can serve application/alps+json.

Hypermedia Control Types

ALPS displays types for each hypermedia control. They include:

Table 1. ALPS types
Type Description

SEMANTIC

A state element (such as HTML.SPAN, HTML.INPUT, and others).

SAFE

A hypermedia control that triggers a safe, idempotent state transition (such as GET or HEAD).

IDEMPOTENT

A hypermedia control that triggers an unsafe, idempotent state transition (such as PUT or DELETE).

UNSAFE

A hypermedia control that triggers an unsafe, non-idempotent state transition (such as POST).

In the representation section shown earlier, bits of data from the application are marked as being SEMANTIC. The address field is a link that involves a safe GET to retrieve. Consequently, it is marked as being SAFE. Hypermedia operations themselves map onto the types as shown in the preceding table.

ALPS with Projections

If you define any projections, they are also listed in the ALPS metadata. Assuming we also defined inlineAddress and noAddresses, they would appear inside the relevant operations. (See “Projections” for the definitions and discussion of these two projections.) That is GET would appear in the operations for the whole collection, and GET would appear in the operations for a single resource. The following example shows the alternate version of the get-persons subsection:

...
  {
    "id" : "get-persons",
    "name" : "persons",
    "type" : "SAFE",
    "rt" : "#person-representation",
    "descriptors" : [ { (1)
      "name" : "projection",
      "doc" : {
        "value" : "The projection that shall be applied when rendering the response. Acceptable values available in nested descriptors.",
        "format" : "TEXT"
      },
      "type" : "SEMANTIC",
      "descriptors" : [ {
        "name" : "inlineAddress", (2)
        "type" : "SEMANTIC",
        "descriptors" : [ {
          "name" : "address",
          "type" : "SEMANTIC"
        }, {
          "name" : "firstName",
          "type" : "SEMANTIC"
        }, {
          "name" : "lastName",
          "type" : "SEMANTIC"
        } ]
      }, {
        "name" : "noAddresses", (3)
        "type" : "SEMANTIC",
        "descriptors" : [ {
          "name" : "firstName",
          "type" : "SEMANTIC"
        }, {
          "name" : "lastName",
          "type" : "SEMANTIC"
        } ]
      } ]
    } ]
  }
...
1 A new attribute, descriptors, appears, containing an array with one entry, projection.
2 Inside the projection.descriptors, we can see inLineAddress. It render address, firstName, and lastName. Relationships rendered inside a projection result in including the data fields inline.
3 noAddresses serves up a subset that contains firstName and lastName.

With all this information, a client can deduce not only the available RESTful transitions but also, to some degree, the data elements needed to interact with the back end.

Adding Custom Details to Your ALPS Descriptions

You can create custom messages that appear in your ALPS metadata. To do so, create rest-messages.properties, as follows:

rest.description.person=A collection of people
rest.description.person.id=primary key used internally to store a person (not for RESTful usage)
rest.description.person.firstName=Person's first name
rest.description.person.lastName=Person's last name
rest.description.person.address=Person's address

These rest.description.* properties define details to display for a Person resource. They alter the ALPS format of the person-representation, as follows:

...
  {
    "id" : "person-representation",
    "doc" : {
      "value" : "A collection of people", (1)
      "format" : "TEXT"
    },
    "descriptors" : [ {
      "name" : "firstName",
      "doc" : {
        "value" : "Person's first name", (2)
        "format" : "TEXT"
      },
      "type" : "SEMANTIC"
    }, {
      "name" : "lastName",
      "doc" : {
        "value" : "Person's last name", (3)
        "format" : "TEXT"
      },
      "type" : "SEMANTIC"
    }, {
      "name" : "id",
      "doc" : {
        "value" : "primary key used internally to store a person (not for RESTful usage)", (4)
        "format" : "TEXT"
      },
      "type" : "SEMANTIC"
    }, {
      "name" : "address",
      "doc" : {
        "value" : "Person's address", (5)
        "format" : "TEXT"
      },
      "type" : "SAFE",
      "rt" : "http://localhost:8080/profile/addresses#address"
    } ]
  }
...
1 The value of rest.description.person maps into the whole representation.
2 The value of rest.description.person.firstName maps to the firstName attribute.
3 The value of rest.description.person.lastName maps to the lastName attribute.
4 The value of rest.description.person.id maps to the id attribute, a field not normally displayed.
5 The value of rest.description.person.address maps to the address attribute.

Supplying these property settings causes each field to have an extra doc attribute.

Spring MVC (which is the essence of a Spring Data REST application) supports locales, meaning you can bundle up multiple properties files with different messages.

JSON Schema

JSON Schema is another form of metadata supported by Spring Data REST. Per their website, JSON Schema has the following advantages:

  • Describes your existing data format

  • Clear, human- and machine-readable documentation

  • Complete structural validation, useful for automated testing and validating client-submitted data

As shown in the previous section, you can reach this data by navigating from the root URI to the profile link.

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/profile"
    },
    "persons" : {
      "href" : "http://localhost:8080/profile/persons"
    },
    "addresses" : {
      "href" : "http://localhost:8080/profile/addresses"
    }
  }
}

These links are the same as shown earlier. To retrieve JSON Schema, you can invoke them with the following Accept header: application/schema+json.

In this case, if you ran curl -H 'Accept:application/schema+json' localhost:8080/profile/persons, you would see output resembling the following:

{
  "title" : "org.springframework.data.rest.webmvc.jpa.Person", (1)
  "properties" : { (2)
    "firstName" : {
      "readOnly" : false,
      "type" : "string"
    },
    "lastName" : {
      "readOnly" : false,
      "type" : "string"
    },
    "siblings" : {
      "readOnly" : false,
      "type" : "string",
      "format" : "uri"
    },
    "created" : {
      "readOnly" : false,
      "type" : "string",
      "format" : "date-time"
    },
    "father" : {
      "readOnly" : false,
      "type" : "string",
      "format" : "uri"
    },
    "weight" : {
      "readOnly" : false,
      "type" : "integer"
    },
    "height" : {
      "readOnly" : false,
      "type" : "integer"
    }
  },
  "descriptors" : { },
  "type" : "object",
  "$schema" : "https://json-schema.org/draft-04/schema#"
}
1 The type that was exported
2 A listing of properties

There are more details if your resources have links to other resources.

You can also find a profile link in the collection of _links when you examine a collection resource, as the following example shows:

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/persons" (1)
    },
    ... other links ...
    "profile" : {
      "href" : "http://localhost:8080/profile/persons" (2)
    }
  },
  ...
}
1 This HAL document respresents the Person collection.
2 It has a profile link to the same URI for metadata.

Again, the profile link serves ALPS by default. If you supply it with an Accept header of application/schema+json, it renders the JSON Schema representation.