diff --git a/li.strolch.website/www.strolch.li/tutorial-configuration.html b/li.strolch.website/www.strolch.li/tutorial-configuration.html index 014c9b273..a6405c547 100644 --- a/li.strolch.website/www.strolch.li/tutorial-configuration.html +++ b/li.strolch.website/www.strolch.li/tutorial-configuration.html @@ -276,21 +276,6 @@ <strolch.env>dev.eitchpc</strolch.env> </properties> </profile> - <profile> - <id>m2e.eitchmac</id> - <activation> - <property> - <name>user.name</name> - <value>eitch</value> - </property> - <os> - <family>mac</family> - </os> - </activation> - <properties> - <strolch.env>dev.eitchmac</strolch.env> - </properties> - </profile> </profiles> </project> @@ -344,10 +329,6 @@ <root>/home/eitch/src/git/strolch-bookshop/runtime</root> <environment>dev</environment> </env> - <env id="dev.eitchmac" default="true"> - <root>/Users/eitch/src/git/strolch-bookshop/runtime</root> - <environment>dev</environment> - </env> </StrolchBootstrap> @@ -408,8 +389,47 @@ <Role name="User"> <Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"> </Privilege> - <Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"> - <Allow>internal</Allow> + + <Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege"> + <AllAllowed>true</AllAllowed> + <Allow>li.strolch.bookshop.search.BookSearch</Allow> + </Privilege> + + <Privilege name="GetResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="GetOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="GetActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> </Privilege> </Role> <Role name="UserPrivileges"> @@ -424,9 +444,48 @@ <Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"> <AllAllowed>true</AllAllowed> </Privilege> - <Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"> - <AllAllowed>true</AllAllowed> + + <Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege"> + <AllAllowed>true</AllAllowed> </Privilege> + + <Privilege name="GetResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="GetOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="GetActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="PrivilegeAddUser" policy="UserAccessPrivilege"> <AllAllowed>true</AllAllowed> </Privilege> @@ -441,12 +500,55 @@ <Allow>li.strolch.runtime.privilege.StrolchSystemActionWithResult</Allow> <Allow>li.strolch.persistence.postgresql.PostgreSqlSchemaInitializer</Allow> </Privilege> + <Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"> <AllAllowed>true</AllAllowed> </Privilege> + <Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"> <AllAllowed>true</AllAllowed> </Privilege> + <Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + + <Privilege name="GetResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="GetOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="GetActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="AddActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="UpdateActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveResource" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveOrder" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="RemoveActivity" policy="ModelPrivilege"> + <AllAllowed>true</AllAllowed> + </Privilege> + <Privilege name="PrivilegeAction" policy="DefaultPrivilege"> <Allow>Persist</Allow> <Allow>PersistSessions</Allow> @@ -657,6 +759,11 @@ unlimited JCE libraries for your JVM and when you restart the server, you don't need to log back in, if your session is still alive. +
PrivilegeRoles.xml
there seems to be a lot of boilerplate. One thing about a highly
+ configurable system is that sometimes the configuration is bigger. In this case we have opted to have
+ the configuration shown and not use default values which you don't see, so that privilege acces is
+ clearly seen.
+ Your project is now ready to be imported into your favourite IDE. We have used both IntelliJ and Eclipse so @@ -669,55 +776,59 @@
package li.strolch.bookshop.web; -import java.io.InputStream; - import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.InputStream; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchBootstrapper; +import li.strolch.utils.helper.StringHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @WebListener public class StartupListener implements ServletContextListener { private static final Logger logger = LoggerFactory.getLogger(StartupListener.class); + private static final String APP_NAME = "Bookshop"; private StrolchAgent agent; @Override public void contextInitialized(ServletContextEvent sce) { - logger.info("Starting Bookshop..."); + logger.info("Starting " + APP_NAME + "..."); + long start = System.currentTimeMillis(); try { - // we load the configuration by reading the boot strap file: String boostrapFileName = "/WEB-INF/" + StrolchBootstrapper.FILE_BOOTSTRAP; InputStream bootstrapFile = sce.getServletContext().getResourceAsStream(boostrapFileName); StrolchBootstrapper bootstrapper = new StrolchBootstrapper(StartupListener.class); - - // now setup, initialize and start Strolch: this.agent = bootstrapper.setupByBoostrapFile(StartupListener.class, bootstrapFile); this.agent.initialize(); this.agent.start(); - - } catch (Exception e) { - logger.error("Failed to start Bookshop due to: " + e.getMessage(), e); + } catch (Throwable e) { + logger.error("Failed to start " + APP_NAME + " due to: " + e.getMessage(), e); throw e; } - logger.info("Started Bookshop."); + long took = System.currentTimeMillis() - start; + logger.info("Started " + APP_NAME + " in " + (StringHelper.formatMillisecondsDuration(took))); } @Override public void contextDestroyed(ServletContextEvent sce) { if (this.agent != null) { - this.agent.stop(); - this.agent.destroy(); + logger.info("Destroying " + APP_NAME + "..."); + try { + this.agent.stop(); + this.agent.destroy(); + } catch (Throwable e) { + logger.error("Failed to stop " + APP_NAME + " due to: " + e.getMessage(), e); + throw e; + } } - logger.info("Destroyed Bookshop."); + logger.info("Destroyed " + APP_NAME); } }@@ -725,7 +836,7 @@ public class StartupListener implements ServletContextListener {
Now configure your IDE to start the web project, and then once it has started, you should see the following in the logs:
-Bookshop:dev All 8 Strolch Components started. Strolch is now ready to be used. Have fun =)) +Bookshop:dev All 8 Strolch Components started. Took 44ms. Strolch is now ready to be used. Have fun =))
This log tells us the name of the app as defined in the StrolchConfiguration.xml file as well as which diff --git a/li.strolch.website/www.strolch.li/tutorial-crud-book.html b/li.strolch.website/www.strolch.li/tutorial-crud-book.html index 89978c1a6..e8b257fcf 100644 --- a/li.strolch.website/www.strolch.li/tutorial-crud-book.html +++ b/li.strolch.website/www.strolch.li/tutorial-crud-book.html @@ -3,7 +3,7 @@
- + @@ -57,7 +57,7 @@Since Books are central to the bookshop, we'll first create the CRUD - REST API for them. The API will be as follows:
+ REST API for them. The API will be as follows:GET ../rest/books?query=,offset=,limit= @@ -68,10 +68,10 @@ DELETE ../rest/books/{id}
Thus corresponding with querying, getting, creating, updating and removing of books. So let's go ahead and - add these REST APIs to our project.
+ add these REST APIs to our project.Our project is using JAX-RS 2.0 as the API and Jersey 2.x as the implementation, thus first we need to - configure JAX-RS. Thus create the following class:
+ configure JAX-RS. Thus create the following class:@ApplicationPath("rest") public class RestfulApplication extends ResourceConfig { @@ -119,10 +119,10 @@ public class BooksResource { }-
The first service we'll add is to query the existing books. The API defines three parameters, with which the - result can be controlled. The method can be defined as follows:
+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:
@GET @@ -135,7 +135,7 @@ public Response query(@Context HttpServletRequest request, @QueryParam("query")
To fill this method we need a few things. First let's define a constants class where we keep String constants - which we used in the model file:
+ which we used in the model file:public class BookShopConstants { @@ -144,44 +144,64 @@ public class BookShopConstants { }-
As this tutorial progesses, 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.
+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.
-Queries in Strolch are their own objects, which allows us to implement privilege validation and thus we need
- to create this class as well. Book entities are Resources, thus we will be creating a
- ResourceQuery
. Since the query is for Resources of type Book, we will define this using a
- navigation. Thus the resulting query looks as follows:
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 ResourceSearch
. The search is for
+ Resources of type Book thus the resulting search looks as follows:
-public class BooksQuery<U> extends ResourceQuery<U> { - public BooksQuery() { - super(new StrolchTypeNavigation(BookShopConstants.TYPE_BOOK)); +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; } }+
Note how we added a special method stringQuery(String)
- this method defines where a search
+ string entered by the user will be used to match a book. In this case for ID
, name
+ and the description
parameter.
So that our users can call this query, we must give them this as a privilege. This is done by adding the full
- class name to the PrivilegeRoles.xml
file as follows:
PrivilegeRoles.xml
file as follows:
... <Role name="User"> - <Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"> + <Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege"> <Allow>internal</Allow> - <Allow>li.strolch.bookshop.query.BooksQuery</Allow> + <Allow>li.strolch.bookshop.search.BookSearch</Allow> </Privilege> </Role> ...
Note: The internal
allow value is a special privilege which is used internally when a
- 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.
-
Now we all parts we need to implement the query method. The method will include opening a transaction, - instantiating the query, executing the query, and returning the result:
+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:
@Path("books") public class BooksResource { @@ -198,46 +218,31 @@ public class BooksResource { int limit = StringHelper.isNotEmpty(limitS) ? Integer.valueOf(limitS) : 0; // open the TX with the certificate, using this class as context + Paging<Resource> paging; try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getClass())) { - // prepare the query - ResourceQuery<Resource> query = new BooksQuery<JsonObject>(); - - // 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<Resource> books = tx.doQuery(query); - - // perform paging - Paging<Resource> paging = Paging.asPage(books, offset, limit); - List<Resource> page = paging.getPage(); - - // return result - ResourceVisitor<JsonObject> visitor = new StrolchRootElementToJsonVisitor().flat().asResourceVisitor(); - return ResponseUtil.listToResponse(StrolchRestfulConstants.DATA, page, a -> a.accept(visitor)); + // perform a book search + paging = new BookSearch() // + .stringQuery(queryS) // + .search(tx) // + .orderByName(false) // + .toPaging(offset, limit); } + + ResourceVisitor<JsonObject> visitor = new StrolchRootElementToJsonVisitor().flat().asResourceVisitor(); + return ResponseUtil.toResponse(paging, e -> e.accept(visitor)); } }
Note: We automatically transform the Resource objects to JSON using the StrolchElementToJsonVisitor
.
- By calling the method .flat()
we have a more compact JSON format. Paging is handled by a util
- class.
.flat()
we have a more compact JSON format. Paging is handled
+ by a util class.
- As a rule we use the format where we return two fields: msg
is a dash if all is ok, otherwise an
- error message will be present. Data is always in the data
field. This is just a personal taste,
- and can be changed to one't taste.
The helper class ResponseUtil
takes care of creating the JsonObject and the proper page. As a
+ rule we use the format where we return two fields: msg
is a dash if all is ok, otherwise an
+ error message will be present. Data is always in the data
field. This is just a personal taste,
+ and can be changed to one's own taste.
Note how we simply retrieve the book as a Resource from the TX. This is a good moment to familiarize yourself
- with the API of the StrolchTransaction
. There are methods to retrieve elements, and also
- perform queries. We will use more of these methods later.
StrolchTransaction
. There are methods to retrieve elements, and also perform
+ queries. We will use more of these methods later.
Further it can be noted that a simple retrieval isn't validated against the user's privileges, the user is - authenticated, which is enough for the moment.
+ authenticated, which is enough for the moment.-public class CreateBookService extends AbstractService{ +public class CreateBookService extends AbstractService<JsonServiceArgument, JsonServiceResult> { private static final long serialVersionUID = 1L; @Override @@ -358,7 +363,7 @@ public class CreateBookService extends AbstractService Note: For the authenticated user to be able to perform this service, we must add it to their - privileges:
+ privileges:... <Role name="User"> @@ -374,7 +379,7 @@ public class CreateBookService extends AbstractServiceUpdate Updating of a book is basically the same as the creation, we just use PUT, verify that the book exists and - give the user the privilege.
+ give the user the privilege.PUT Method:
@@ -556,21 +561,22 @@ public class RemoveBookService extends AbstractService<StringServiceArgument,Notes:
One should now see a pattern emerge:
-
- The REST API delegates to the Services, or queries, with the exception of the retrieval of a single +
- The REST API delegates to the Services, or Searches, with the exception of the retrieval of a single object by id.
- Services should do initial validation of the input. Not much validation was done here, but more could be done.
- Commands are reusable objects to perform recurring work.
-- Queries and Services are privileged actions for which a user must have the privilege to perform the +
- Searches and Services are privileged actions for which a user must have the privilege to perform the action.
The book services are quite simple, but as more requirements arise, it should be easy to implement them in - 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.
+ 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).This concludes the CRUD of books.
@@ -614,7 +620,7 @@ public class RemoveBookService extends AbstractService<StringServiceArgument, s.parentNode.insertBefore(g, s); })(); - +