diff --git a/runtime/config/PrivilegeRoles.xml b/runtime/config/PrivilegeRoles.xml
index fc26159..107810c 100644
--- a/runtime/config/PrivilegeRoles.xml
+++ b/runtime/config/PrivilegeRoles.xml
@@ -3,9 +3,13 @@
+ li.strolch.bookshop.service.CreateBookService
+ li.strolch.bookshop.service.UpdateBookService
+ li.strolch.bookshop.service.RemoveBookService
internal
+ li.strolch.bookshop.query.BooksQuery
diff --git a/runtime/data/templates.xml b/runtime/data/templates.xml
index 8d90d0a..e4ffe68 100644
--- a/runtime/data/templates.xml
+++ b/runtime/data/templates.xml
@@ -44,7 +44,7 @@
-
+
diff --git a/src/main/java/li/strolch/bookshop/BookShopConstants.java b/src/main/java/li/strolch/bookshop/BookShopConstants.java
new file mode 100644
index 0000000..b4563dc
--- /dev/null
+++ b/src/main/java/li/strolch/bookshop/BookShopConstants.java
@@ -0,0 +1,11 @@
+package li.strolch.bookshop;
+
+public class BookShopConstants {
+
+ public static final String TYPE_BOOK = "Book";
+
+ public static final String BAG_PARAMETERS = "parameters";
+
+ public static final String PARAM_DESCRIPTION = "description";
+
+}
diff --git a/src/main/java/li/strolch/bookshop/query/BooksQuery.java b/src/main/java/li/strolch/bookshop/query/BooksQuery.java
new file mode 100644
index 0000000..f1d646f
--- /dev/null
+++ b/src/main/java/li/strolch/bookshop/query/BooksQuery.java
@@ -0,0 +1,12 @@
+package li.strolch.bookshop.query;
+
+import li.strolch.bookshop.BookShopConstants;
+import li.strolch.model.query.ResourceQuery;
+import li.strolch.model.query.StrolchTypeNavigation;
+
+public class BooksQuery extends ResourceQuery {
+
+ public BooksQuery() {
+ super(new StrolchTypeNavigation(BookShopConstants.TYPE_BOOK));
+ }
+}
diff --git a/src/main/java/li/strolch/bookshop/rest/BooksResource.java b/src/main/java/li/strolch/bookshop/rest/BooksResource.java
new file mode 100644
index 0000000..d9df346
--- /dev/null
+++ b/src/main/java/li/strolch/bookshop/rest/BooksResource.java
@@ -0,0 +1,189 @@
+package li.strolch.bookshop.rest;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import li.strolch.bookshop.BookShopConstants;
+import li.strolch.bookshop.query.BooksQuery;
+import li.strolch.bookshop.service.CreateBookService;
+import li.strolch.bookshop.service.RemoveBookService;
+import li.strolch.bookshop.service.UpdateBookService;
+import li.strolch.model.Resource;
+import li.strolch.model.json.StrolchElementToJsonVisitor;
+import li.strolch.model.query.NameSelection;
+import li.strolch.model.query.OrSelection;
+import li.strolch.model.query.ParameterSelection;
+import li.strolch.model.query.ResourceQuery;
+import li.strolch.persistence.api.StrolchTransaction;
+import li.strolch.privilege.model.Certificate;
+import li.strolch.rest.RestfulStrolchComponent;
+import li.strolch.rest.StrolchRestfulConstants;
+import li.strolch.rest.helper.ResponseUtil;
+import li.strolch.rest.util.JsonServiceArgument;
+import li.strolch.rest.util.JsonServiceResult;
+import li.strolch.service.StringServiceArgument;
+import li.strolch.service.api.ServiceHandler;
+import li.strolch.service.api.ServiceResult;
+import li.strolch.utils.StringMatchMode;
+import li.strolch.utils.collections.Paging;
+import li.strolch.utils.helper.StringHelper;
+
+@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) : 20;
+
+ // open the TX with the certificate, using this class as context
+ try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getClass())) {
+
+ // prepare the query
+ ResourceQuery query = new BooksQuery() //
+ // set transformation to JSON
+ .setVisitor(new StrolchElementToJsonVisitor().flat());
+
+ // prepare selections
+ if (StringHelper.isEmpty(queryS)) {
+ query.withAny();
+ } else {
+ OrSelection or = new OrSelection();
+ or.with(ParameterSelection.stringSelection(BookShopConstants.BAG_PARAMETERS,
+ BookShopConstants.PARAM_DESCRIPTION, queryS, StringMatchMode.ci()));
+ or.with(new NameSelection(queryS, StringMatchMode.ci()));
+
+ // add selections
+ query.with(or);
+ }
+
+ // perform the query
+ List books = tx.doQuery(query);
+
+ // perform paging
+ Paging page = Paging.asPage(books, offset, limit);
+
+ // return result
+ return ResponseUtil.toResponse(StrolchRestfulConstants.DATA, page.getPage());
+ }
+ }
+
+ @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
+ JsonObject bookJ = book.accept(new StrolchElementToJsonVisitor().flat());
+
+ // return
+ return ResponseUtil.toResponse(StrolchRestfulConstants.DATA, bookJ);
+ }
+ }
+
+ @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);
+ }
+
+ @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);
+ }
+
+ @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);
+ }
+}
diff --git a/src/main/java/li/strolch/bookshop/service/CreateBookService.java b/src/main/java/li/strolch/bookshop/service/CreateBookService.java
new file mode 100644
index 0000000..4820a58
--- /dev/null
+++ b/src/main/java/li/strolch/bookshop/service/CreateBookService.java
@@ -0,0 +1,57 @@
+package li.strolch.bookshop.service;
+
+import com.google.gson.JsonObject;
+
+import li.strolch.bookshop.BookShopConstants;
+import li.strolch.command.AddResourceCommand;
+import li.strolch.model.Resource;
+import li.strolch.model.json.FromFlatJsonVisitor;
+import li.strolch.model.json.StrolchElementToJsonVisitor;
+import li.strolch.persistence.api.StrolchTransaction;
+import li.strolch.rest.util.JsonServiceArgument;
+import li.strolch.rest.util.JsonServiceResult;
+import li.strolch.service.api.AbstractService;
+
+public class CreateBookService extends AbstractService {
+
+ 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
+ new FromFlatJsonVisitor().visit(book, arg.jsonElement.getAsJsonObject());
+
+ // add command to store the resource
+ AddResourceCommand cmd = new AddResourceCommand(getContainer(), tx);
+ cmd.setResource(book);
+ tx.addCommand(cmd);
+
+ // 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);
+ }
+}
diff --git a/src/main/java/li/strolch/bookshop/service/RemoveBookService.java b/src/main/java/li/strolch/bookshop/service/RemoveBookService.java
new file mode 100644
index 0000000..5b8f3a3
--- /dev/null
+++ b/src/main/java/li/strolch/bookshop/service/RemoveBookService.java
@@ -0,0 +1,46 @@
+package li.strolch.bookshop.service;
+
+import li.strolch.bookshop.BookShopConstants;
+import li.strolch.command.RemoveResourceCommand;
+import li.strolch.model.Resource;
+import li.strolch.persistence.api.StrolchTransaction;
+import li.strolch.service.StringServiceArgument;
+import li.strolch.service.api.AbstractService;
+import li.strolch.service.api.ServiceResult;
+
+public class RemoveBookService extends AbstractService {
+
+ 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);
+
+ // add command to remove the resource
+ RemoveResourceCommand cmd = new RemoveResourceCommand(getContainer(), tx);
+ cmd.setResource(book);
+ tx.addCommand(cmd);
+
+ // notify the TX that it should commit on close
+ tx.commitOnClose();
+ }
+
+ // and return the result
+ return ServiceResult.success();
+ }
+}
diff --git a/src/main/java/li/strolch/bookshop/service/UpdateBookService.java b/src/main/java/li/strolch/bookshop/service/UpdateBookService.java
new file mode 100644
index 0000000..a8b008d
--- /dev/null
+++ b/src/main/java/li/strolch/bookshop/service/UpdateBookService.java
@@ -0,0 +1,63 @@
+package li.strolch.bookshop.service;
+
+import com.google.gson.JsonObject;
+
+import li.strolch.bookshop.BookShopConstants;
+import li.strolch.command.UpdateResourceCommand;
+import li.strolch.model.Resource;
+import li.strolch.model.Tags.Json;
+import li.strolch.model.json.FromFlatJsonVisitor;
+import li.strolch.model.json.StrolchElementToJsonVisitor;
+import li.strolch.persistence.api.StrolchTransaction;
+import li.strolch.rest.util.JsonServiceArgument;
+import li.strolch.rest.util.JsonServiceResult;
+import li.strolch.service.api.AbstractService;
+import li.strolch.utils.dbc.DBC;
+
+public class UpdateBookService extends AbstractService {
+
+ 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
+ new FromFlatJsonVisitor().visit(book, arg.jsonElement.getAsJsonObject());
+
+ // add command to update the resource
+ UpdateResourceCommand cmd = new UpdateResourceCommand(getContainer(), tx);
+ cmd.setResource(book);
+ tx.addCommand(cmd);
+
+ // 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);
+ }
+}
diff --git a/src/main/java/li/strolch/bookshop/web/RestfulApplication.java b/src/main/java/li/strolch/bookshop/web/RestfulApplication.java
new file mode 100644
index 0000000..246b600
--- /dev/null
+++ b/src/main/java/li/strolch/bookshop/web/RestfulApplication.java
@@ -0,0 +1,54 @@
+package li.strolch.bookshop.web;
+
+import java.util.logging.Level;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.Priorities;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+
+import li.strolch.bookshop.rest.BooksResource;
+import li.strolch.rest.RestfulStrolchComponent;
+import li.strolch.rest.StrolchRestfulExceptionMapper;
+import li.strolch.rest.endpoint.AuthenticationService;
+import li.strolch.rest.filters.AccessControlResponseFilter;
+import li.strolch.rest.filters.AuthenticationRequestFilter;
+import li.strolch.rest.filters.AuthenticationResponseFilter;
+import li.strolch.rest.filters.CharsetResponseFilter;
+import li.strolch.rest.filters.HttpCacheResponseFilter;
+
+@ApplicationPath("rest")
+public class RestfulApplication extends ResourceConfig {
+
+ public RestfulApplication() {
+
+ // add strolch resources
+ register(AuthenticationService.class);
+
+ // 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");
+ }
+ }
+}