Table of Contents

1.0 Controller Implementation

Controller implementation

A clean implementation expects two parameters, and accepts two optional parameters.

The method postVerifyNotExists method should be overriden to set ids from the path if necessary. E.g. with a DynamoDB implemenation.

You can either extend the controller to do specific overrides like this example:

public class TestModelRESTController extends RestControllerImpl<TestModel> {
    public TestModelRESTController(JsonObject appConfig, Repository<TestModel> repository) {
        super(TestModel.class, appConfig, repository);
    }
}

or use the RestControllerImpl class directly for a standard REST controller (DynamoDBRepository as example):

Repository<TestModel> repository = new DynamoDBRepository<>(TestModel.class, config());
RestControllerImpl<TestModel> = new RestControllerImpl<TestModel>(TestModel.class, config(), repository);

1.0.1 Intercepting executions in the controller

The controller can be overriden at any point in its execution to perform particular business logic. When overriding it’s methods, remember to call the next method in the chain to continue execution. This enables async operations at any point in execution.

Intercept methods:

Show

default void preShow(RoutingContext routingContext) {
    performShow(routingContext);
}

Index

default void preIndex(RoutingContext routingContext, String customQuery) {
    prepareQuery(routingContext, customQuery);
}

default void preProcessQuery(RoutingContext routingContext, Map<String, List<String>> queryMap) {
    processQuery(routingContext, queryMap);
}

default void postProcessQuery(RoutingContext routingContext, AggregateFunction aggregateFunction,
                              Queue<OrderByParameter> orderByQueue, Map<String, List<FilterParameter>> params,
                              @Nonnull String[] projections, String indexName, Integer limit) {
    postPrepareQuery(routingContext, aggregateFunction, orderByQueue, params, projections, indexName, limit);
}

default void postPrepareQuery(RoutingContext routingContext, AggregateFunction aggregateFunction,
                              Queue<OrderByParameter> orderByQueue, Map<String, List<FilterParameter>> params,
                              String[] projections, String indexName, Integer limit) {
    createIdObjectForIndex(routingContext, aggregateFunction, orderByQueue, params, projections, indexName, limit);
}

Create

default void preCreate(RoutingContext routingContext) {
    if (denyQuery(routingContext)) return;

    parseBodyForCreate(routingContext);
}

default void preVerifyNotExists(E newRecord, RoutingContext routingContext) {
    verifyNotExists(newRecord, routingContext);
}

default void postVerifyNotExists(E newRecord, RoutingContext routingContext) {
    preSetIdentifiers(newRecord, routingContext);
}

default void preSetIdentifiers(E newRecord, RoutingContext routingContext) {
    setIdentifiers(newRecord, routingContext);
}

default void preSanitizeForCreate(E record, RoutingContext routingContext) {
    performSanitizeForCreate(record, routingContext);
}

default void performSanitizeForCreate(E record, RoutingContext routingContext) {
    record.sanitize();

    postSanitizeForCreate(record, routingContext);
}

default void postSanitizeForCreate(E record, RoutingContext routingContext) {
    preValidateForCreate(record, routingContext);
}

default void preValidateForCreate(E record, RoutingContext routingContext) {
    performValidateForCreate(record, routingContext);
}

default void postValidateForCreate(E record, RoutingContext routingContext) {
    performCreate(record, routingContext);
}

default void postCreate(@Nonnull E createdRecord, RoutingContext routingContext) {
    long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

    routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
    routingContext.put(BODY_CONTENT_TAG, createdRecord.toJsonString());
    setStatusCodeAndContinue(201, routingContext, initialNanoTime);
}

Update

default void preUpdate(RoutingContext routingContext) {
    if (denyQuery(routingContext)) return;

    parseBodyForUpdate(routingContext);
}

default void preVerifyExistsForUpdate(E newRecord, RoutingContext routingContext) {
    verifyExistsForUpdate(newRecord, routingContext);
}

default void postVerifyExistsForUpdate(E oldRecord, E newRecord, RoutingContext routingContext) {
    preSanitizeForUpdate(oldRecord, newRecord, routingContext);
}

default void preSanitizeForUpdate(E record, E newRecord, RoutingContext routingContext) {
    performSanitizeForUpdate(record, newRecord, routingContext);
}

default void performSanitizeForUpdate(E record, E newRecord, RoutingContext routingContext) {
    Function<E, E> setNewValues = rec -> {
        rec.setModifiables(newRecord);
        rec.sanitize();

        return rec;
    };

    postSanitizeForUpdate(setNewValues.apply(record), setNewValues, routingContext);
}

default void postSanitizeForUpdate(E record, Function<E, E> setNewValues, RoutingContext routingContext) {
    preValidateForUpdate(record, setNewValues, routingContext);
}

default void preValidateForUpdate(E record, Function<E, E> setNewValues, RoutingContext routingContext) {
    performValidateForUpdate(record, setNewValues, routingContext);
}

default void postValidateForUpdate(E record, Function<E, E> setNewValues, RoutingContext routingContext) {
    performUpdate(record, setNewValues, routingContext);
}

void performUpdate(E updatedRecord, Function<E, E> setNewValues, RoutingContext routingContext);

default void postUpdate(@Nonnull E updatedRecord, RoutingContext routingContext) {
    long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

    routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
    routingContext.put(BODY_CONTENT_TAG, updatedRecord.toJsonString());
    setStatusCodeAndContinue(200, routingContext, initialNanoTime);
}

Delete

default void preDestroy(RoutingContext routingContext) {
    if (denyQuery(routingContext)) return;

    verifyExistsForDestroy(routingContext);
}

void verifyExistsForDestroy(RoutingContext routingContext);

default void postVerifyExistsForDestroy(E recordForDestroy, RoutingContext routingContext) {
    performDestroy(recordForDestroy, routingContext);
}

default void postDestroy(@Nonnull E destroyedRecord, RoutingContext routingContext) {
    long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

    setStatusCodeAndContinue(204, routingContext, initialNanoTime);
}

1.1 Querying the Controller

Querying

Show

Show operations returns the JSON representation of a single object.

Index

All index operations on the API can be performed with finegrained filtering and ordering as well as aggregation with or without grouping, for doing more advanced searches.

Index operations return an itemList with the following format:

{
    "etag" : "string",
    "pageToken" : "string",
    "count" : "integer",
    "items" : "array of objects in JSON representation"
}

1.1.1 Filtering

Filtering

Examples

{
  "eq":15000
}


[{
  "gt":10000
}, {
  "lt":5000,
  "type":"or"
}]


[{
  "gt":10000
}, {
  "lt":20000,
  "type":"and"
}] 
Above examples as Url Encoded Json. (commentCount as field)

commentCount=%7B%22eq%22%3A15000%7D

commentCount=%5B%7B%22gt%22%3A10000%7D%2C%7B%22lt%22%3A5000%2C%22type%22%3A%22or%22%7D%5D

commentCount=%5B%7B%22gt%22%3A10000%7D%2C%7B%22lt%22%3A20000%2C%22type%22%3A%22and%22%7D%5D

1.1.2 Ordering

Ordering

Examples
{
  "field":"commentCount"
}


{
  "field":"commentCount",
  "direction":"asc"
} 
Above examples as Url Encoded Json. (commentCount as field)

orderBy=%7B%22field%22%3A%22commentCount%22%7D

orderBy=%7B%22field%22%3A%22commentCount%22%2C%22direction%22%3A%22asc%22%7D

1.1.3 Aggregation

Aggregation

{
  "count": "<integer>",
  "results": [
      "groupByKey":"<valueOfGroupByKeyElement>",
      "<aggregateFunction>":"<aggregateValue>"
    ]
} 
{
  "avg":"<avgValue>"
}

{
  "sum":"<sumValue>"
}

{
  "count":"<countValue>"
} 
Query Examples
{
  "field":"commentCount",
  "function":"MIN"
}

{
  "field":"likeCount",
  "function":"COUNT",
  "groupBy": [
    {
      "groupBy":"userId"
    }
  ]
}

{
  "function":"COUNT"
} 
Above examples as Url Encoded Json. (commentCount as field)

aggregate=%7B%22field%22%3A%22commentCount%22%2C%22function%22%3A%22MIN%22%7D

aggregate=%7B%22field%22%3A%22likeCount%22%2C%22function%22%3A%22COUNT%22%2C%22groupBy%22%3A%5B%7B%22groupBy%22%3A%22userId%22%7D%5D%7D

aggregate=%7B%22function%22%3A%22COUNT%22%7D

1.1.4 Projection

#### Projection

{
  "fields":["someField","someOtherField","someThirdField"]
} 
Examples
{
  "fields":["feedId"]
}

{
  "fields":["providerFeedItemId","likeCount","feedId"]
} 
Above examples as Url Encoded Json.

projection=%7B%22fields%22%3A%5B%22feedId%22%5D%7D

projection=%7B%22fields%22%3A%5B%22providerFeedItemId%22%2C%22likeCount%22%2C%22feedId%22%5D%7D

1.2 Cross Model Querying

1.2.1 Cross Model Aggregations

Cross-Model Aggregation

Query Examples
{
  "function":"COUNT",
  "groupBy": [
    {
      "groupBy": ["userId"]
    }
  ]
}

{
  "function":"SUM",
  "groupBy": [
    {
      "groupBy": ["feedId"],
      "groupingSortOrder":"asc",
      "groupingListLimit":10
    }
  ]
}

{
  "function":"COUNT",
  "groupBy": [
    "groupBy":["comments.registrationDate", "likes.createdAt"],
    "groupByUnit":"DATE",
    "groupByRange":"WEEK",
    "groupingSortOrder":"asc",
    "groupingListLimit":10
  ]
} 
Above examples as Url Encoded Json.

aggregate=%7B%22function%22%3A%22COUNT%22%2C%22groupBy%22%3A%5B%7B%22groupBy%22%3A%5B%22userId%22%5D%7D%5D%7D

aggregate=%7B%22function%22%3A%22SUM%22%2C%22groupBy%22%3A%5B%7B%22groupBy%22%3A%5B%22feedId%22%5D%2C%22groupBySortOrder%22%3A%22asc%22%2C%22groupByListLimit%22%3A10%7D%5D%7D

aggregate=%7B%22function%22%3A%22COUNT%22%2C%22groupBy%22%3A%5B%22groupBy%22%3A%5B%22comments.registrationDate%2Clikes.createdAt%22%5D%2C%22groupByUnit%22%3A%22DATE%22%2C%22groupByRange%22%3A%22WEEK%22%2C%22groupBySortOrder%22%3A%22asc%22%2C%22groupByListLimit%22%3A10%5D%7D

Query Examples
{
  "models":["comments", "likes"]
}

{
  "models":["feedItems", "comments"],
  "fields":["feedItems.likeCount","comments.likeCount"]
} 
Above examples as Url Encoded Json.

projection=%7B%22models%22%3A%5B%22comments%22%2C%20%22likes%22%5D%7D

projection=%7B%22models%22%3A%5B%22feedItems%22%2C%20%22comments%22%5D%2C%22fields%22%3A%5B%22feedItems.likeCount%22%2C%22comments.likeCount%22%5D%7D

Query Examples
{
  "models":[
    {
      "model":"feedItems",
      "fields":[
        {
          "field":"providerName",
          "parameters":[
            {
              "eq":"FACEBOOK"
            }
          ]
        }
      ]
    }
  ]
}

{
  "models":[
    {
      "model":"comments",
      "fields":[
        {
          "field":"reply",
          "parameters":[
            {
              "eq":true
            }
          ]
        },
        {
          "field":"likeCount",
          "parameters":[
            {
              "gt":10000
            },
            {
              "lt":50000
            }
          ]
        }
      ]
    },
    {
      "model":"likes",
      "fields":[
        {
          "field":"userId",
          "parameters":[
            {
              "eq":"4554b1eda02f902beea73cd03c4acb4"
            },
            {
              "eq":"6ab6c2a487d25c6c314774a845690e6",
              "type":"or"
            }
          ]
        }
      ]
    }
  ]
} 
Above examples as Url Encoded Json.

projection=%7B%22models%22%3A%5B%7B%22model%22%3A%22feedItems%22%2C%22fields%22%3A%5B%7B%22field%22%3A%22providerName%22%2C%22parameters%22%3A%5B%7B%22eq%22%3A%22FACEBOOK%22%20%7D%5D%7D%5D%7D%5D%7D

projection=%7B%22models%22%3A%5B%7B%22model%22%3A%22comments%22%2C%22fields%22%3A%5B%7B%22field%22%3A%22reply%22%2C%22parameters%22%3A%5B%7B%22eq%22%3Atrue%7D%5D%7D%2C%7B%22field%22%3A%22likeCount%22%2C%22parameters%22%3A%5B%7B%22gt%22%3A10000%7D%2C%7B%22lt%22%3A50000%7D%5D%7D%5D%7D%2C%7B%22model%22%3A%22likes%22%2C%22fields%22%3A%5B%7B%22field%22%3A%22userId%22%2C%22parameters%22%3A%5B%7B%22eq%22%3A%224554b1eda02f902beea73cd03c4acb4%22%7D%2C%7B%22eq%22%3A%226ab6c2a487d25c6c314774a845690e6%22%2C%22type%22%3A%22or%22%7D%5D%7D%5D%7D%5D%7D