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"); + } + } +}