Skip to content
This repository has been archived by the owner on Apr 13, 2019. It is now read-only.

library update

Konstantin Sobolev edited this page May 4, 2017 · 2 revisions

Update operation

Update operation will take a map with book updates as an input and return a map of updated books as an output.

Schema

Operation schema is is shown below. Things to notice:

  • keys are required for the input projection (in case input projection is specified in the request at all)
  • keys are forbidden for the output projection: output will always contain keys from the input data
  • output projection reuses $bookProjection (see search action)
  update {
    inputProjection [ required ] (
      title,
      author:id,
      text:plain
    )

    outputProjection [ forbidden ] ( $bookProjection )
  }

Operation implementation

Operation implementation goes as follows: we extract all fields from the incoming BookRecord instance, apply them on top of existing book state and write resuling instance back to the backend. BooksBuilder is then used to re-read updated instances and construct the response according to the output projection.

public class BooksUpdateOperation extends AbstractUpdateOperation {
  BooksUpdateOperation(@NotNull UpdateOperationDeclaration declaration) {
    super(declaration);
  }

  @Override
  protected @NotNull CompletableFuture<BookId_BookRecord_Map.Data> process(
      @NotNull BookId_BookRecord_Map.Builder.Data responseBuilder,
      @NotNull BookId_BookRecord_Map updateData,
      @Nullable UpdateBooksFieldProjection updateProjection,
      @NotNull OutputBooksFieldProjection outputProjection) {

    OutputBookRecordProjection bookOutputProjection = outputProjection.dataProjection().itemsProjection();

    BookId_BookRecord_Map.Builder booksMapBuilder = BookId_BookRecord_Map.create();
    responseBuilder.set(booksMapBuilder);

    for (Map.Entry<BookId.Imm, ? extends BookRecord.Value> entry : updateData.values().entrySet()) {
      BookId.Imm bookId = entry.getKey();
      BookRecord bookUpdate = entry.getValue().getDatum();

      assert bookUpdate != null; // ensured by framework

      BooksBackend.BookData bookData = BooksBackend.get(bookId);
      if (bookData == null) {
        booksMapBuilder.putError(
            bookId,
            new ErrorValue(HttpStatusCode.NOT_FOUND, "Book " + bookId.getVal() + " not found")
        );
      } else {
        // values from the update request
        Optional<String> title = Optional.ofNullable(bookUpdate.getTitle());
        Optional<AuthorId> author = Optional.ofNullable(bookUpdate.getAuthor()).map(Author::getId);
        Optional<String> text = Optional.ofNullable(bookUpdate.getText()).map(Text::getPlain).map(PlainText::getVal);

        // new fields state: either updates or existing data
        String newTitle = title.orElse(bookData.title);
        AuthorId newAuthorId = author.orElse(bookData.authorId);
        String newText = text.orElse(bookData.text);

        // check author ID
        if (AuthorsBackend.get(newAuthorId) == null) {
          booksMapBuilder.putError(
              bookId,
              new ErrorValue(HttpStatusCode.BAD_REQUEST, "Author " + newAuthorId.getVal() + " not found")
          );
        } else {
          BooksBackend.set(
              bookId,
              new BooksBackend.BookData(bookId, newTitle, newAuthorId, newText)
          );
          // return updated book record
          booksMapBuilder.put_(bookId, BookBuilder.buildBook(bookId, bookOutputProjection));
        }

      }

    }

    return CompletableFuture.completedFuture(responseBuilder);
  }
}

Last step is to plug it into BooksResourceFactory:

  @Override
  protected @NotNull UpdateOperation<BookId_BookRecord_Map.Data> constructUpdateOperation(
      @NotNull UpdateOperationDeclaration operationDeclaration) throws ServiceInitializationException {
    return new BooksUpdateOperation(operationDeclaration);
  }

Running

Lets modify two books in one call, updating title for 1 and author for 2. Lets use invalid author ID to see how errors are reported

curl -s -g -X PUT -d '[{"K":1,"V":{"title":"New Title"}},{"K":2,"V":{"author":14}}]' "http://localhost:8888/books>[](title,author:id)" | jq
[
  {
    "K": 1,
    "V": {
      "title": "New Title",
      "author": 1
    }
  },
  {
    "K": 2,
    "V": {
      "ERROR": 400,
      "message": "Author 14 not found"
    }
  }
]

Notice how > is used to separate operation path from output projection. Full version with update projection specified too would look like this:

http://localhost:8888/books<[1,2](title,author:id)>[](title,author:id)

Next section: delete operation