2017-05-21 21:47:49 +02:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="utf-8">
|
|
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
2018-06-22 18:15:11 +02:00
|
|
|
<meta name="google-site-verification" content="CPhbjooaiTdROm7Vs4E7kuHZvBfkeLUtonGgcVUbTL8" />
|
2017-05-21 21:47:49 +02:00
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
<meta name="description" content="">
|
|
|
|
<meta name="author" content="">
|
|
|
|
<link rel="shortcut icon" href="ico/favicon.ico">
|
|
|
|
|
|
|
|
<title>Strolch: Tutorial CRUD Book</title>
|
|
|
|
|
|
|
|
<!-- Bootstrap core CSS -->
|
|
|
|
<link href="css/bootstrap.min.css" rel="stylesheet">
|
|
|
|
|
|
|
|
<!-- Custom styles for this template -->
|
|
|
|
<link href="css/custom.css" rel="stylesheet">
|
|
|
|
|
|
|
|
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --><!--[if lt IE 9]>
|
|
|
|
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
|
|
|
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script><![endif]-->
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
|
|
|
<div class="container">
|
|
|
|
<div class="navbar-header">
|
|
|
|
<a class="navbar-brand" href="index.html">Strolch</a>
|
|
|
|
</div>
|
|
|
|
<div class="collapse navbar-collapse">
|
|
|
|
<ul class="nav navbar-nav">
|
|
|
|
<li><a href="index.html">Overview</a></li>
|
|
|
|
<li><a href="api.html">API</a></li>
|
|
|
|
<li><a href="documentation.html">Documentation</a></li>
|
|
|
|
<li class="active"><a href="tutorial.html">Tutorial</a></li>
|
|
|
|
<li><a href="downloads.html">Downloads</a></li>
|
|
|
|
<li><a href="development.html">Development</a></li>
|
|
|
|
<li><a href="blog.html">Blog</a></li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
<!--/.nav-collapse -->
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
|
|
<div class="page-header">
|
|
|
|
<h1 class="page-title">Tutorial: CRUD Book</h1>
|
|
|
|
|
|
|
|
<p class="lead page-description">Writing the CRUD services for books.</p>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="content">
|
|
|
|
<a href="tutorial-model.html" class="pull-left">Previous: Model</a> <br><br>
|
|
|
|
|
2017-06-01 12:41:55 +02:00
|
|
|
<h3>Preparation</h3>
|
|
|
|
|
|
|
|
<p>Since Books are central to the bookshop, we'll first create the <a target="_blank"
|
|
|
|
href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a>
|
2018-06-22 18:15:11 +02:00
|
|
|
REST API for them. The API will be as follows:</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<pre>
|
|
|
|
GET ../rest/books?query=,offset=,limit=
|
|
|
|
GET ../rest/books/{id}
|
|
|
|
POST ../rest/books
|
|
|
|
PUT ../rest/books/{id}
|
|
|
|
DELETE ../rest/books/{id}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p>Thus corresponding with querying, getting, creating, updating and removing of books. So let's go ahead and
|
2018-06-22 18:15:11 +02:00
|
|
|
add these REST APIs to our project.</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<p>Our project is using JAX-RS 2.0 as the API and Jersey 2.x as the implementation, thus first we need to
|
2018-06-22 18:15:11 +02:00
|
|
|
configure JAX-RS. Thus create the following class:</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
<pre class="pre-scrollable">
|
|
|
|
@ApplicationPath("rest")
|
|
|
|
public class RestfulApplication extends ResourceConfig {
|
|
|
|
|
|
|
|
public RestfulApplication() {
|
|
|
|
|
|
|
|
// add strolch resources
|
|
|
|
register(AuthenticationService.class);
|
2018-02-27 19:00:13 +01:00
|
|
|
register(ModelQuery.class);
|
|
|
|
register(Inspector.class);
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
// add project resources by package name
|
|
|
|
packages(BooksResource.class.getPackage().getName());
|
|
|
|
|
|
|
|
// filters
|
|
|
|
register(AuthenticationRequestFilter.class, Priorities.AUTHENTICATION);
|
|
|
|
register(AccessControlResponseFilter.class);
|
|
|
|
register(AuthenticationResponseFilter.class);
|
|
|
|
register(HttpCacheResponseFilter.class);
|
|
|
|
|
|
|
|
// log exceptions and return them as plain text to the caller
|
|
|
|
register(StrolchRestfulExceptionMapper.class);
|
|
|
|
|
|
|
|
// the JSON generated is in UTF-8
|
|
|
|
register(CharsetResponseFilter.class);
|
|
|
|
|
|
|
|
RestfulStrolchComponent restfulComponent = RestfulStrolchComponent.getInstance();
|
|
|
|
if (restfulComponent.isRestLogging()) {
|
|
|
|
register(new LoggingFeature(java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME),
|
|
|
|
Level.SEVERE, LoggingFeature.Verbosity.PAYLOAD_ANY, Integer.MAX_VALUE));
|
|
|
|
|
|
|
|
property(ServerProperties.TRACING, "ALL");
|
|
|
|
property(ServerProperties.TRACING_THRESHOLD, "TRACE");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
<p>As we add new resources they will be automatically since we register the entire package.</p>
|
|
|
|
|
|
|
|
<p>Now add the books resource class:</p>
|
|
|
|
<pre>
|
|
|
|
@Path("books")
|
|
|
|
public class BooksResource {
|
|
|
|
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
2018-06-22 18:15:11 +02:00
|
|
|
<h3>Search</h3>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
2018-06-22 18:15:11 +02:00
|
|
|
<p>The first service we'll add is to query, or search for the existing books. The API defines three parameters,
|
|
|
|
with which the result can be controlled. The method can be defined as follows:</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<pre>
|
|
|
|
@GET
|
|
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
|
|
public Response query(@Context HttpServletRequest request, @QueryParam("query") String queryS,
|
|
|
|
@QueryParam("offset") String offsetS, @QueryParam("limit") String limitS) {
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p>To fill this method we need a few things. First let's define a constants class where we keep String constants
|
2018-06-22 18:15:11 +02:00
|
|
|
which we used in the model file:</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
<pre>
|
|
|
|
public class BookShopConstants {
|
|
|
|
|
|
|
|
public static final String TYPE_BOOK = "Book";
|
|
|
|
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
2018-06-22 18:15:11 +02:00
|
|
|
<p>As this tutorial progresses, more and more constants will be added here. This class helps with two issues:
|
|
|
|
Through the constants we can easily reason over where certain fields, and types are used and of course String
|
|
|
|
literals in code are a rather bad thing.</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
2018-06-22 18:15:11 +02:00
|
|
|
<p>In Strolch there are multiple way to access objects. The old way was using Queries, the new search API is
|
|
|
|
much more fluent and easier to read and write. The search API, as well as the deprecated query API allows us
|
|
|
|
to implement privilege validation and thus one should create corresponding classes for each type of search.
|
|
|
|
Book entities are Resources, thus we will be creating a <code>ResourceSearch</code>. The search is for
|
|
|
|
Resources of type Book thus the resulting search looks as follows:</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
<pre>
|
2018-06-22 18:15:11 +02:00
|
|
|
public class BooksSearch<U> extends ResourceSearch<U> {
|
|
|
|
public BookSearch() {
|
|
|
|
types(TYPE_BOOK);
|
|
|
|
}
|
|
|
|
|
|
|
|
public BookSearch stringQuery(String value) {
|
|
|
|
if (isEmpty(value))
|
|
|
|
return this;
|
|
|
|
|
|
|
|
// split by spaces
|
|
|
|
value = value.trim();
|
|
|
|
String[] values = value.split(" ");
|
|
|
|
|
|
|
|
// add where clauses for id, name and description
|
|
|
|
where(id().containsIgnoreCase(values) //
|
|
|
|
.or(name().containsIgnoreCase(values)) //
|
|
|
|
.or(param(BAG_PARAMETERS, PARAM_DESCRIPTION).containsIgnoreCase(values)));
|
|
|
|
|
|
|
|
return this;
|
2017-06-01 12:41:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
2018-06-22 18:15:11 +02:00
|
|
|
<p>Note how we added a special method <code>stringQuery(String)</code> - this method defines where a search
|
|
|
|
string entered by the user will be used to match a book. In this case for <code>ID</code>, <code>name</code>
|
|
|
|
and the <code>description</code> parameter.</p>
|
|
|
|
|
2017-06-01 12:41:55 +02:00
|
|
|
<p>So that our users can call this query, we must give them this as a privilege. This is done by adding the full
|
2018-06-22 18:15:11 +02:00
|
|
|
class name to the <code>PrivilegeRoles.xml</code> file as follows:</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<pre>
|
|
|
|
...
|
|
|
|
<Role name="User">
|
2018-06-22 18:15:11 +02:00
|
|
|
<Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege">
|
2017-06-01 12:41:55 +02:00
|
|
|
<Allow>internal</Allow>
|
2018-06-22 18:15:11 +02:00
|
|
|
<Allow>li.strolch.bookshop.search.BookSearch</Allow>
|
2017-06-01 12:41:55 +02:00
|
|
|
</Privilege>
|
|
|
|
</Role>
|
|
|
|
...
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p><b>Note:</b> The <code>internal</code> allow value is a special privilege which is used internally when a
|
2018-06-22 18:15:11 +02:00
|
|
|
service or something performs internal queries. This means that a service can perform a query
|
|
|
|
for object to which the user might not have access, but without which the service could not be
|
|
|
|
completed. We will use this in a later stage. </p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
2018-06-22 18:15:11 +02:00
|
|
|
<p>Now we have all parts we need to implement the query method. The method will include opening a transaction,
|
|
|
|
instantiating the search, executing the search, and returning the result:</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
<pre class="pre-scrollable">
|
|
|
|
@Path("books")
|
|
|
|
public class BooksResource {
|
|
|
|
|
|
|
|
@GET
|
|
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
|
|
public Response query(@Context HttpServletRequest request, @QueryParam("query") String queryS,
|
|
|
|
@QueryParam("offset") String offsetS, @QueryParam("limit") String limitS) {
|
|
|
|
|
|
|
|
// this is an authenticated method call, thus we can get the certificate from the request:
|
|
|
|
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
|
|
|
|
|
|
|
|
int offset = StringHelper.isNotEmpty(offsetS) ? Integer.valueOf(offsetS) : 0;
|
|
|
|
int limit = StringHelper.isNotEmpty(limitS) ? Integer.valueOf(limitS) : 0;
|
|
|
|
|
|
|
|
// open the TX with the certificate, using this class as context
|
2018-06-22 18:15:11 +02:00
|
|
|
Paging<Resource> paging;
|
2017-06-01 12:41:55 +02:00
|
|
|
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getClass())) {
|
|
|
|
|
2018-06-22 18:15:11 +02:00
|
|
|
// perform a book search
|
|
|
|
paging = new BookSearch() //
|
|
|
|
.stringQuery(queryS) //
|
|
|
|
.search(tx) //
|
|
|
|
.orderByName(false) //
|
|
|
|
.toPaging(offset, limit);
|
2017-06-01 12:41:55 +02:00
|
|
|
}
|
2018-06-22 18:15:11 +02:00
|
|
|
|
|
|
|
ResourceVisitor<JsonObject> visitor = new StrolchRootElementToJsonVisitor().flat().asResourceVisitor();
|
|
|
|
return ResponseUtil.toResponse(paging, e -> e.accept(visitor));
|
2017-06-01 12:41:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p><b>Note:</b> We automatically transform the Resource objects to JSON using the <code>StrolchElementToJsonVisitor</code>.
|
2018-06-22 18:15:11 +02:00
|
|
|
By calling the method <code>.flat()</code> we have a more compact JSON format. Paging is handled
|
|
|
|
by a util class.</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
2018-06-22 18:15:11 +02:00
|
|
|
<p>The helper class <code>ResponseUtil</code> takes care of creating the JsonObject and the proper page. As a
|
|
|
|
rule we use the format where we return two fields: <code>msg</code> is a dash if all is ok, otherwise an
|
|
|
|
error message will be present. Data is always in the <code>data</code> field. This is just a personal taste,
|
|
|
|
and can be changed to one's own taste.</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<h3>Get</h3>
|
|
|
|
|
|
|
|
We have all we need now to implement the GET method:
|
|
|
|
|
|
|
|
<pre class="pre-scrollable">
|
|
|
|
@GET
|
|
|
|
@Path("{id}")
|
|
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
|
|
public Response get(@Context HttpServletRequest request, @PathParam("id") String id) {
|
|
|
|
|
|
|
|
// this is an authenticated method call, thus we can get the certificate from the request:
|
|
|
|
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
|
|
|
|
|
|
|
|
// open the TX with the certificate, using this class as context
|
|
|
|
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getClass())) {
|
|
|
|
|
|
|
|
// get the book
|
|
|
|
Resource book = tx.getResourceBy(BookShopConstants.TYPE_BOOK, id);
|
|
|
|
if (book == null)
|
|
|
|
return ResponseUtil.toResponse(Status.NOT_FOUND, "Book " + id + " does not exist!");
|
|
|
|
|
|
|
|
// transform to JSON
|
2018-02-27 19:00:13 +01:00
|
|
|
JsonObject bookJ = book.accept(new StrolchRootElementToJsonVisitor().flat());
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
// return
|
|
|
|
return ResponseUtil.toResponse(StrolchRestfulConstants.DATA, bookJ);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p>Note how we simply retrieve the book as a Resource from the TX. This is a good moment to familiarize yourself
|
2018-06-22 18:15:11 +02:00
|
|
|
with the API of the <code>StrolchTransaction</code>. There are methods to retrieve elements, and also perform
|
|
|
|
queries. We will use more of these methods later.</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<p>Further it can be noted that a simple retrieval isn't validated against the user's privileges, the user is
|
2018-06-22 18:15:11 +02:00
|
|
|
authenticated, which is enough for the moment.</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<h3>Create</h3>
|
|
|
|
|
|
|
|
To create a new book we need to implement a <code>Service</code>. This service will be called <code>CreateBookService</code>.
|
|
|
|
A Service always has a <code>ServiceArgument</code> and a <code>ServiceResult</code>. Our service will use the
|
|
|
|
<code>JsonServiceArgument</code> and the <code>JsonServiceResult</code>. The implementation of the POST method
|
|
|
|
is as follows:
|
|
|
|
|
|
|
|
<pre class="pre-scrollable">
|
|
|
|
@POST
|
|
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
|
|
public Response create(@Context HttpServletRequest request, String data) {
|
|
|
|
|
|
|
|
// this is an authenticated method call, thus we can get the certificate from the request:
|
|
|
|
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
|
|
|
|
|
|
|
|
// parse data to JSON
|
|
|
|
JsonObject jsonData = new JsonParser().parse(data).getAsJsonObject();
|
|
|
|
|
|
|
|
// instantiate the service with the argument
|
|
|
|
CreateBookService svc = new CreateBookService();
|
|
|
|
JsonServiceArgument arg = svc.getArgumentInstance();
|
|
|
|
arg.jsonElement = jsonData;
|
|
|
|
|
|
|
|
// perform the service
|
|
|
|
ServiceHandler serviceHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
|
|
|
|
JsonServiceResult result = serviceHandler.doService(cert, svc, arg);
|
|
|
|
|
|
|
|
// return depending on the result state
|
|
|
|
if (result.isOk())
|
|
|
|
return ResponseUtil.toResponse(StrolchRestfulConstants.DATA, result.getResult());
|
|
|
|
return ResponseUtil.toResponse(result);
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p><b>Note:</b> We return the created object again as JSON in its own data field.</p>
|
|
|
|
|
|
|
|
The service is implemented as follows:
|
|
|
|
<pre class="pre-scrollable">
|
2018-06-22 18:15:11 +02:00
|
|
|
public class CreateBookService extends AbstractService<JsonServiceArgument, JsonServiceResult> {
|
2017-06-01 12:41:55 +02:00
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected JsonServiceResult getResultInstance() {
|
|
|
|
return new JsonServiceResult();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public JsonServiceArgument getArgumentInstance() {
|
|
|
|
return new JsonServiceArgument();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected JsonServiceResult internalDoService(JsonServiceArgument arg) throws Exception {
|
|
|
|
|
|
|
|
// open a new transaction, using the realm from the argument, or the certificate
|
|
|
|
Resource book;
|
|
|
|
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
|
|
|
|
|
|
|
|
// get a new book "instance" from the template
|
|
|
|
book = tx.getResourceTemplate(BookShopConstants.TYPE_BOOK);
|
|
|
|
|
|
|
|
// map all values from the JSON object into the new book element
|
2018-02-27 19:00:13 +01:00
|
|
|
book.accept(new FromFlatJsonVisitor(arg.jsonElement.getAsJsonObject()).ignoreBag(BAG_RELATIONS));
|
2017-06-01 12:41:55 +02:00
|
|
|
|
2018-02-27 19:00:13 +01:00
|
|
|
// save changes
|
|
|
|
tx.add(book);
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
// notify the TX that it should commit on close
|
|
|
|
tx.commitOnClose();
|
|
|
|
}
|
|
|
|
|
|
|
|
// map the return value to JSON
|
|
|
|
JsonObject result = book.accept(new StrolchElementToJsonVisitor().flat());
|
|
|
|
|
|
|
|
// and return the result
|
|
|
|
return new JsonServiceResult(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p><b>Note:</b> For the authenticated user to be able to perform this service, we must add it to their
|
2018-06-22 18:15:11 +02:00
|
|
|
privileges:</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
<pre>
|
|
|
|
...
|
|
|
|
<Role name="User">
|
|
|
|
...
|
|
|
|
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
|
|
|
|
<Allow>li.strolch.bookshop.service.CreateBookService</Allow>
|
|
|
|
</Privilege>
|
|
|
|
...
|
|
|
|
</Role>
|
|
|
|
...
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<h3>Update</h3>
|
|
|
|
|
|
|
|
<p>Updating of a book is basically the same as the creation, we just use PUT, verify that the book exists and
|
2018-06-22 18:15:11 +02:00
|
|
|
give the user the privilege.</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<p><b>PUT Method:</b></p>
|
|
|
|
<pre class="pre-scrollable">
|
|
|
|
@PUT
|
|
|
|
@Path("{id}")
|
|
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
|
|
public Response update(@Context HttpServletRequest request, @PathParam("id") String id, String data) {
|
|
|
|
|
|
|
|
// this is an authenticated method call, thus we can get the certificate from the request:
|
|
|
|
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
|
|
|
|
|
|
|
|
// parse data to JSON
|
|
|
|
JsonObject jsonData = new JsonParser().parse(data).getAsJsonObject();
|
|
|
|
|
|
|
|
// instantiate the service with the argument
|
|
|
|
UpdateBookService svc = new UpdateBookService();
|
|
|
|
JsonServiceArgument arg = svc.getArgumentInstance();
|
|
|
|
arg.objectId = id;
|
|
|
|
arg.jsonElement = jsonData;
|
|
|
|
|
|
|
|
// perform the service
|
|
|
|
ServiceHandler serviceHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
|
|
|
|
JsonServiceResult result = serviceHandler.doService(cert, svc, arg);
|
|
|
|
|
|
|
|
// return depending on the result state
|
|
|
|
if (result.isOk())
|
|
|
|
return ResponseUtil.toResponse(StrolchRestfulConstants.DATA, result.getResult());
|
|
|
|
return ResponseUtil.toResponse(result);
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p><b>Update Service:</b></p>
|
|
|
|
<pre class="pre-scrollable">
|
|
|
|
public class UpdateBookService extends AbstractService<JsonServiceArgument, JsonServiceResult> {
|
|
|
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected JsonServiceResult getResultInstance() {
|
|
|
|
return new JsonServiceResult();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public JsonServiceArgument getArgumentInstance() {
|
|
|
|
return new JsonServiceArgument();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected JsonServiceResult internalDoService(JsonServiceArgument arg) throws Exception {
|
|
|
|
|
|
|
|
// verify same book
|
|
|
|
DBC.PRE.assertEquals("ObjectId and given Id must be same!", arg.objectId,
|
|
|
|
arg.jsonElement.getAsJsonObject().get(Json.ID).getAsString());
|
|
|
|
|
|
|
|
// open a new transaction, using the realm from the argument, or the certificate
|
|
|
|
Resource book;
|
|
|
|
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
|
|
|
|
|
|
|
|
// get the existing book
|
|
|
|
book = tx.getResourceBy(BookShopConstants.TYPE_BOOK, arg.objectId, true);
|
|
|
|
|
|
|
|
// map all values from the JSON object into the new book element
|
2018-02-27 19:00:13 +01:00
|
|
|
book.accept(new FromFlatJsonVisitor(arg.jsonElement.getAsJsonObject()).ignoreBag(BAG_RELATIONS));
|
2017-06-01 12:41:55 +02:00
|
|
|
|
2018-02-27 19:00:13 +01:00
|
|
|
// save changes
|
|
|
|
tx.update(book);
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
// notify the TX that it should commit on close
|
|
|
|
tx.commitOnClose();
|
|
|
|
}
|
|
|
|
|
|
|
|
// map the return value to JSON
|
|
|
|
JsonObject result = book.accept(new StrolchElementToJsonVisitor().flat());
|
|
|
|
|
|
|
|
// and return the result
|
|
|
|
return new JsonServiceResult(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p><b>Privilege:</b></p>
|
|
|
|
<pre>
|
|
|
|
...
|
|
|
|
<Role name="User">
|
|
|
|
...
|
|
|
|
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
|
|
|
|
...
|
|
|
|
<Allow>li.strolch.bookshop.service.UpdateBookService</Allow>
|
|
|
|
...
|
|
|
|
</Privilege>
|
|
|
|
...
|
|
|
|
</Role>
|
|
|
|
...
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>Remove</h3>
|
|
|
|
|
|
|
|
<p>To remove a book, we need a DELETE method, a remove service and the associated privilege.</p>
|
|
|
|
|
|
|
|
|
|
|
|
<p><b>DELETE Method:</b></p>
|
|
|
|
<pre class="pre-scrollable">
|
|
|
|
@DELETE
|
|
|
|
@Path("{id}")
|
|
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
|
|
public Response update(@Context HttpServletRequest request, @PathParam("id") String id) {
|
|
|
|
|
|
|
|
// this is an authenticated method call, thus we can get the certificate from the request:
|
|
|
|
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
|
|
|
|
|
|
|
|
// instantiate the service with the argument
|
|
|
|
RemoveBookService svc = new RemoveBookService();
|
|
|
|
StringServiceArgument arg = svc.getArgumentInstance();
|
|
|
|
arg.value = id;
|
|
|
|
|
|
|
|
// perform the service
|
|
|
|
ServiceHandler serviceHandler = RestfulStrolchComponent.getInstance().getServiceHandler();
|
|
|
|
ServiceResult result = serviceHandler.doService(cert, svc, arg);
|
|
|
|
|
|
|
|
// return depending on the result state
|
|
|
|
return ResponseUtil.toResponse(result);
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p><b>Remove Service:</b></p>
|
|
|
|
<pre class="pre-scrollable">
|
|
|
|
public class RemoveBookService extends AbstractService<StringServiceArgument, ServiceResult> {
|
|
|
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected ServiceResult getResultInstance() {
|
|
|
|
return new ServiceResult();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public StringServiceArgument getArgumentInstance() {
|
|
|
|
return new StringServiceArgument();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected ServiceResult internalDoService(StringServiceArgument arg) throws Exception {
|
|
|
|
|
|
|
|
// open a new transaction, using the realm from the argument, or the certificate
|
|
|
|
try (StrolchTransaction tx = openArgOrUserTx(arg)) {
|
|
|
|
|
|
|
|
// get the existing book
|
|
|
|
Resource book = tx.getResourceBy(BookShopConstants.TYPE_BOOK, arg.value, true);
|
|
|
|
|
2018-02-27 19:00:13 +01:00
|
|
|
// save changes
|
|
|
|
tx.remove(book);
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
// notify the TX that it should commit on close
|
|
|
|
tx.commitOnClose();
|
|
|
|
}
|
|
|
|
|
|
|
|
// and return the result
|
|
|
|
return ServiceResult.success();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<p><b>Privilege:</b></p>
|
|
|
|
<pre>
|
|
|
|
...
|
|
|
|
<Role name="User">
|
|
|
|
...
|
|
|
|
<Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege">
|
|
|
|
...
|
|
|
|
<Allow>li.strolch.bookshop.service.RemoveBookService</Allow>
|
|
|
|
...
|
|
|
|
</Privilege>
|
|
|
|
...
|
|
|
|
</Role>
|
|
|
|
...
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<h3>Notes:</h3>
|
|
|
|
<p>One should now see a pattern emerge:</p>
|
|
|
|
<ul>
|
2018-06-22 18:15:11 +02:00
|
|
|
<li>The REST API delegates to the Services, or Searches, with the exception of the retrieval of a single
|
2017-06-01 12:41:55 +02:00
|
|
|
object by id.
|
|
|
|
</li>
|
|
|
|
<li>Services should do initial validation of the input. Not much validation was done here, but more could be
|
|
|
|
done.
|
|
|
|
</li>
|
|
|
|
<li>Commands are reusable objects to perform recurring work.</li>
|
2018-06-22 18:15:11 +02:00
|
|
|
<li>Searches and Services are privileged actions for which a user must have the privilege to perform the
|
2017-06-01 12:41:55 +02:00
|
|
|
action.
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
<p>The book services are quite simple, but as more requirements arise, it should be easy to implement them in
|
2018-06-22 18:15:11 +02:00
|
|
|
the service layer. Thus should a service be required to be performed by an integration layer, then they can
|
|
|
|
simply call the services, since the input is defined and validation is done there (i.e. NOT in the REST
|
|
|
|
API).</p>
|
2017-06-01 12:41:55 +02:00
|
|
|
|
|
|
|
<p>This concludes the CRUD of books.</p>
|
2017-05-21 21:47:49 +02:00
|
|
|
|
|
|
|
<a href="tutorial-model.html" class="pull-left">Previous: Model</a>
|
|
|
|
<!-- content here -->
|
|
|
|
|
|
|
|
<a href="tutorial-model.html"></a>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<!-- /.content -->
|
|
|
|
|
|
|
|
<div id="footer">
|
|
|
|
<div class="container">
|
|
|
|
<p class="text-muted">© Strolch / <a href="mailto:eitch@eitchnet.ch">Robert von Burg</a> / Hosting by
|
|
|
|
<a href="http://www.eitchnet.ch">eitchnet.ch</a></p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<!-- /.container -->
|
|
|
|
|
|
|
|
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
|
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
|
|
|
|
<!-- Include all compiled plugins (below), or include individual xsd as needed -->
|
|
|
|
<script src="js/bootstrap.min.js"></script>
|
|
|
|
|
|
|
|
<!-- Piwik -->
|
|
|
|
<script type="text/javascript">
|
|
|
|
var _paq = _paq || [];
|
|
|
|
_paq.push(['trackPageView']);
|
|
|
|
_paq.push(['enableLinkTracking']);
|
|
|
|
(function () {
|
|
|
|
var u = (("https:" == document.location.protocol) ? "https" : "http") + "://piwik.eitchnet.ch/";
|
|
|
|
_paq.push(['setTrackerUrl', u + 'piwik.php']);
|
|
|
|
_paq.push(['setSiteId', 2]);
|
|
|
|
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
|
|
|
g.type = 'text/javascript';
|
|
|
|
g.defer = true;
|
|
|
|
g.async = true;
|
|
|
|
g.src = u + 'piwik.js';
|
|
|
|
s.parentNode.insertBefore(g, s);
|
|
|
|
})();
|
|
|
|
</script>
|
2018-06-22 18:15:11 +02:00
|
|
|
<noscript><p><img src="http://piwik.eitchnet.ch/piwik.php?idsite=2" style="border:0;" alt="" /></p></noscript>
|
2017-05-21 21:47:49 +02:00
|
|
|
<!-- End Piwik Code -->
|
|
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|