[Major] Update website tutorial to not use queries

This commit is contained in:
Robert von Burg 2018-06-22 18:15:11 +02:00
parent 1c2d048c45
commit f51d5ae81f
2 changed files with 226 additions and 109 deletions

View File

@ -276,21 +276,6 @@
<strolch.env>dev.eitchpc</strolch.env> <strolch.env>dev.eitchpc</strolch.env>
</properties> </properties>
</profile> </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> </profiles>
</project> </project>
</pre> </pre>
@ -344,10 +329,6 @@
&lt;root&gt;/home/eitch/src/git/strolch-bookshop/runtime&lt;/root&gt; &lt;root&gt;/home/eitch/src/git/strolch-bookshop/runtime&lt;/root&gt;
&lt;environment&gt;dev&lt;/environment&gt; &lt;environment&gt;dev&lt;/environment&gt;
&lt;/env&gt; &lt;/env&gt;
&lt;env id="dev.eitchmac" default="true"&gt;
&lt;root&gt;/Users/eitch/src/git/strolch-bookshop/runtime&lt;/root&gt;
&lt;environment&gt;dev&lt;/environment&gt;
&lt;/env>
&lt;/StrolchBootstrap> &lt;/StrolchBootstrap>
</pre> </pre>
@ -408,8 +389,47 @@
&lt;Role name="User"&gt; &lt;Role name="User"&gt;
&lt;Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"&gt; &lt;Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
&lt;Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"&gt;
&lt;Allow&gt;internal&lt;/Allow&gt; &lt;Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege"&gt;
&lt;AllAllowed>true&lt;/AllAllowed&gt;
&lt;Allow&gt;li.strolch.bookshop.search.BookSearch&lt;/Allow&gt;
&lt;/Privilege&gt;
&lt;Privilege name="GetResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="GetOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="GetActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
&lt;/Role&gt; &lt;/Role&gt;
&lt;Role name="UserPrivileges"&gt; &lt;Role name="UserPrivileges"&gt;
@ -424,9 +444,48 @@
&lt;Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"&gt; &lt;Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt; &lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
&lt;Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt; &lt;Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege"&gt;
&lt;AllAllowed>true&lt;/AllAllowed&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
&lt;Privilege name="GetResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="GetOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="GetActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="PrivilegeAddUser" policy="UserAccessPrivilege"&gt; &lt;Privilege name="PrivilegeAddUser" policy="UserAccessPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt; &lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
@ -441,12 +500,55 @@
&lt;Allow&gt;li.strolch.runtime.privilege.StrolchSystemActionWithResult&lt;/Allow&gt; &lt;Allow&gt;li.strolch.runtime.privilege.StrolchSystemActionWithResult&lt;/Allow&gt;
&lt;Allow&gt;li.strolch.persistence.postgresql.PostgreSqlSchemaInitializer&lt;/Allow&gt; &lt;Allow&gt;li.strolch.persistence.postgresql.PostgreSqlSchemaInitializer&lt;/Allow&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
&lt;Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"&gt; &lt;Privilege name="li.strolch.service.api.Service" policy="DefaultPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt; &lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
&lt;Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"&gt; &lt;Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt; &lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
&lt;Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege"&gt;
&lt;AllAllowed>true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="GetResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="GetOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="GetActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="AddActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="UpdateActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveResource" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveOrder" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="RemoveActivity" policy="ModelPrivilege"&gt;
&lt;AllAllowed&gt;true&lt;/AllAllowed&gt;
&lt;/Privilege&gt;
&lt;Privilege name="PrivilegeAction" policy="DefaultPrivilege"&gt; &lt;Privilege name="PrivilegeAction" policy="DefaultPrivilege"&gt;
&lt;Allow&gt;Persist&lt;/Allow&gt; &lt;Allow&gt;Persist&lt;/Allow&gt;
&lt;Allow&gt;PersistSessions&lt;/Allow&gt; &lt;Allow&gt;PersistSessions&lt;/Allow&gt;
@ -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 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. your session is still alive.
</li> </li>
<li>In <code>PrivilegeRoles.xml</code> 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.
</li>
</ul> </ul>
<p>Your project is now ready to be imported into your favourite IDE. We have used both IntelliJ and Eclipse so <p>Your project is now ready to be imported into your favourite IDE. We have used both IntelliJ and Eclipse so
@ -669,55 +776,59 @@
<pre class="pre-scrollable"> <pre class="pre-scrollable">
package li.strolch.bookshop.web; package li.strolch.bookshop.web;
import java.io.InputStream;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener; import javax.servlet.annotation.WebListener;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchAgent;
import li.strolch.agent.api.StrolchBootstrapper; import li.strolch.agent.api.StrolchBootstrapper;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@WebListener @WebListener
public class StartupListener implements ServletContextListener { public class StartupListener implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(StartupListener.class); private static final Logger logger = LoggerFactory.getLogger(StartupListener.class);
private static final String APP_NAME = "Bookshop";
private StrolchAgent agent; private StrolchAgent agent;
@Override @Override
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
logger.info("Starting Bookshop..."); logger.info("Starting " + APP_NAME + "...");
long start = System.currentTimeMillis();
try { try {
// we load the configuration by reading the boot strap file:
String boostrapFileName = "/WEB-INF/" + StrolchBootstrapper.FILE_BOOTSTRAP; String boostrapFileName = "/WEB-INF/" + StrolchBootstrapper.FILE_BOOTSTRAP;
InputStream bootstrapFile = sce.getServletContext().getResourceAsStream(boostrapFileName); InputStream bootstrapFile = sce.getServletContext().getResourceAsStream(boostrapFileName);
StrolchBootstrapper bootstrapper = new StrolchBootstrapper(StartupListener.class); StrolchBootstrapper bootstrapper = new StrolchBootstrapper(StartupListener.class);
// now setup, initialize and start Strolch:
this.agent = bootstrapper.setupByBoostrapFile(StartupListener.class, bootstrapFile); this.agent = bootstrapper.setupByBoostrapFile(StartupListener.class, bootstrapFile);
this.agent.initialize(); this.agent.initialize();
this.agent.start(); this.agent.start();
} catch (Throwable e) {
} catch (Exception e) { logger.error("Failed to start " + APP_NAME + " due to: " + e.getMessage(), e);
logger.error("Failed to start Bookshop due to: " + e.getMessage(), e);
throw e; throw e;
} }
logger.info("Started Bookshop."); long took = System.currentTimeMillis() - start;
logger.info("Started " + APP_NAME + " in " + (StringHelper.formatMillisecondsDuration(took)));
} }
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { public void contextDestroyed(ServletContextEvent sce) {
if (this.agent != null) { if (this.agent != null) {
this.agent.stop(); logger.info("Destroying " + APP_NAME + "...");
this.agent.destroy(); 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);
} }
} }
</pre> </pre>
@ -725,7 +836,7 @@ public class StartupListener implements ServletContextListener {
<p>Now configure your IDE to start the web project, and then once it has started, you should see the following <p>Now configure your IDE to start the web project, and then once it has started, you should see the following
in the logs:</p> in the logs:</p>
<pre class="pre-scrollable"> <pre class="pre-scrollable">
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 =))
</pre> </pre>
<p>This log tells us the name of the app as defined in the StrolchConfiguration.xml file as well as which <p>This log tells us the name of the app as defined in the StrolchConfiguration.xml file as well as which

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="google-site-verification" content="CPhbjooaiTdROm7Vs4E7kuHZvBfkeLUtonGgcVUbTL8"/> <meta name="google-site-verification" content="CPhbjooaiTdROm7Vs4E7kuHZvBfkeLUtonGgcVUbTL8" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
@ -57,7 +57,7 @@
<p>Since Books are central to the bookshop, we'll first create the <a target="_blank" <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> href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a>
REST API for them. The API will be as follows:</p> REST API for them. The API will be as follows:</p>
<pre> <pre>
GET ../rest/books?query=,offset=,limit= GET ../rest/books?query=,offset=,limit=
@ -68,10 +68,10 @@ DELETE ../rest/books/{id}
</pre> </pre>
<p>Thus corresponding with querying, getting, creating, updating and removing of books. So let's go ahead and <p>Thus corresponding with querying, getting, creating, updating and removing of books. So let's go ahead and
add these REST APIs to our project.</p> add these REST APIs to our project.</p>
<p>Our project is using JAX-RS 2.0 as the API and Jersey 2.x as the implementation, thus first we need to <p>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:</p> configure JAX-RS. Thus create the following class:</p>
<pre class="pre-scrollable"> <pre class="pre-scrollable">
@ApplicationPath("rest") @ApplicationPath("rest")
public class RestfulApplication extends ResourceConfig { public class RestfulApplication extends ResourceConfig {
@ -119,10 +119,10 @@ public class BooksResource {
} }
</pre> </pre>
<h3>Query</h3> <h3>Search</h3>
<p>The first service we'll add is to query the existing books. The API defines three parameters, with which the <p>The first service we'll add is to query, or search for the existing books. The API defines three parameters,
result can be controlled. The method can be defined as follows:</p> with which the result can be controlled. The method can be defined as follows:</p>
<pre> <pre>
@GET @GET
@ -135,7 +135,7 @@ public Response query(@Context HttpServletRequest request, @QueryParam("query")
</pre> </pre>
<p>To fill this method we need a few things. First let's define a constants class where we keep String constants <p>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:</p> which we used in the model file:</p>
<pre> <pre>
public class BookShopConstants { public class BookShopConstants {
@ -144,44 +144,64 @@ public class BookShopConstants {
} }
</pre> </pre>
<p>As this tutorial progesses, more and more constants will be added here. This class helps with two issues: <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 Through the constants we can easily reason over where certain fields, and types are used and of course String
String literals in code are a rather bad thing.</p> literals in code are a rather bad thing.</p>
<p>Queries in Strolch are their own objects, which allows us to implement privilege validation and thus we need <p>In Strolch there are multiple way to access objects. The old way was using Queries, the new search API is
to create this class as well. Book entities are Resources, thus we will be creating a much more fluent and easier to read and write. The search API, as well as the deprecated query API allows us
<code>ResourceQuery</code>. Since the query is for Resources of type Book, we will define this using a to implement privilege validation and thus one should create corresponding classes for each type of search.
navigation. Thus the resulting query looks as follows:</p> 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>
<pre> <pre>
public class BooksQuery&lt;U&gt; extends ResourceQuery&lt;U&gt; { public class BooksSearch&lt;U&gt; extends ResourceSearch&lt;U&gt; {
public BooksQuery() { public BookSearch() {
super(new StrolchTypeNavigation(BookShopConstants.TYPE_BOOK)); 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;
} }
} }
</pre> </pre>
<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>
<p>So that our users can call this query, we must give them this as a privilege. This is done by adding the full <p>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 <code>PrivilegeRoles.xml</code> file as follows:</p> class name to the <code>PrivilegeRoles.xml</code> file as follows:</p>
<pre> <pre>
... ...
&lt;Role name="User"&gt; &lt;Role name="User"&gt;
&lt;Privilege name="li.strolch.model.query.StrolchQuery" policy="DefaultPrivilege"&gt; &lt;Privilege name="li.strolch.search.StrolchSearch" policy="DefaultPrivilege"&gt;
&lt;Allow&gt;internal&lt;/Allow&gt; &lt;Allow&gt;internal&lt;/Allow&gt;
&lt;Allow&gt;li.strolch.bookshop.query.BooksQuery&lt;/Allow&gt; &lt;Allow&gt;li.strolch.bookshop.search.BookSearch&lt;/Allow&gt;
&lt;/Privilege&gt; &lt;/Privilege&gt;
&lt;/Role&gt; &lt;/Role&gt;
... ...
</pre> </pre>
<p><b>Note:</b> The <code>internal</code> allow value is a special privilege which is used internally when a <p><b>Note:</b> The <code>internal</code> 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 service or something performs internal queries. This means that a service can perform a query
which the user might not have access, but without which the service could not be completed. We will use this for object to which the user might not have access, but without which the service could not be
in a later stage. completed. We will use this in a later stage. </p>
</p>
<p>Now we all parts we need to implement the query method. The method will include opening a transaction, <p>Now we have 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:</p> instantiating the search, executing the search, and returning the result:</p>
<pre class="pre-scrollable"> <pre class="pre-scrollable">
@Path("books") @Path("books")
public class BooksResource { public class BooksResource {
@ -198,46 +218,31 @@ public class BooksResource {
int limit = StringHelper.isNotEmpty(limitS) ? Integer.valueOf(limitS) : 0; int limit = StringHelper.isNotEmpty(limitS) ? Integer.valueOf(limitS) : 0;
// open the TX with the certificate, using this class as context // open the TX with the certificate, using this class as context
Paging&lt;Resource&gt; paging;
try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getClass())) { try (StrolchTransaction tx = RestfulStrolchComponent.getInstance().openTx(cert, getClass())) {
// prepare the query // perform a book search
ResourceQuery&lt;Resource&gt; query = new BooksQuery&lt;JsonObject&gt;(); paging = new BookSearch() //
.stringQuery(queryS) //
// prepare selections .search(tx) //
if (StringHelper.isEmpty(queryS)) { .orderByName(false) //
query.withAny(); .toPaging(offset, limit);
} 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&lt;Resource&gt; books = tx.doQuery(query);
// perform paging
Paging&lt;Resource&gt; paging = Paging.asPage(books, offset, limit);
List&lt;Resource&gt; page = paging.getPage();
// return result
ResourceVisitor&lt;JsonObject&gt; visitor = new StrolchRootElementToJsonVisitor().flat().asResourceVisitor();
return ResponseUtil.listToResponse(StrolchRestfulConstants.DATA, page, a -&gt; a.accept(visitor));
} }
ResourceVisitor&lt;JsonObject&gt; visitor = new StrolchRootElementToJsonVisitor().flat().asResourceVisitor();
return ResponseUtil.toResponse(paging, e -&gt; e.accept(visitor));
} }
} }
</pre> </pre>
<p><b>Note:</b> We automatically transform the Resource objects to JSON using the <code>StrolchElementToJsonVisitor</code>. <p><b>Note:</b> We automatically transform the Resource objects to JSON using the <code>StrolchElementToJsonVisitor</code>.
By calling the method <code>.flat()</code> we have a more compact JSON format. Paging is handled by a util By calling the method <code>.flat()</code> we have a more compact JSON format. Paging is handled
class.</p> by a util class.</p>
<p>As a rule we use the format where we return two fields: <code>msg</code> is a dash if all is ok, otherwise an <p>The helper class <code>ResponseUtil</code> takes care of creating the JsonObject and the proper page. As a
error message will be present. Data is always in the <code>data</code> field. This is just a personal taste, rule we use the format where we return two fields: <code>msg</code> is a dash if all is ok, otherwise an
and can be changed to one't taste.</p> 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>
<h3>Get</h3> <h3>Get</h3>
@ -270,11 +275,11 @@ public Response get(@Context HttpServletRequest request, @PathParam("id") String
</pre> </pre>
<p>Note how we simply retrieve the book as a Resource from the TX. This is a good moment to familiarize yourself <p>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 <code>StrolchTransaction</code>. There are methods to retrieve elements, and also with the API of the <code>StrolchTransaction</code>. There are methods to retrieve elements, and also perform
perform queries. We will use more of these methods later.</p> queries. We will use more of these methods later.</p>
<p>Further it can be noted that a simple retrieval isn't validated against the user's privileges, the user is <p>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.</p> authenticated, which is enough for the moment.</p>
<h3>Create</h3> <h3>Create</h3>
@ -314,7 +319,7 @@ public Response create(@Context HttpServletRequest request, String data) {
The service is implemented as follows: The service is implemented as follows:
<pre class="pre-scrollable"> <pre class="pre-scrollable">
public class CreateBookService extends AbstractService<JsonServiceArgument, JsonServiceResult> { public class CreateBookService extends AbstractService&lt;JsonServiceArgument, JsonServiceResult&gt; {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Override @Override
@ -358,7 +363,7 @@ public class CreateBookService extends AbstractService<JsonServiceArgument, Json
</pre> </pre>
<p><b>Note:</b> For the authenticated user to be able to perform this service, we must add it to their <p><b>Note:</b> For the authenticated user to be able to perform this service, we must add it to their
privileges:</p> privileges:</p>
<pre> <pre>
... ...
&lt;Role name="User"&gt; &lt;Role name="User"&gt;
@ -374,7 +379,7 @@ public class CreateBookService extends AbstractService<JsonServiceArgument, Json
<h3>Update</h3> <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 <p>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.</p> give the user the privilege.</p>
<p><b>PUT Method:</b></p> <p><b>PUT Method:</b></p>
<pre class="pre-scrollable"> <pre class="pre-scrollable">
@ -556,21 +561,22 @@ public class RemoveBookService extends AbstractService&lt;StringServiceArgument,
<h3>Notes:</h3> <h3>Notes:</h3>
<p>One should now see a pattern emerge:</p> <p>One should now see a pattern emerge:</p>
<ul> <ul>
<li>The REST API delegates to the Services, or queries, with the exception of the retrieval of a single <li>The REST API delegates to the Services, or Searches, with the exception of the retrieval of a single
object by id. object by id.
</li> </li>
<li>Services should do initial validation of the input. Not much validation was done here, but more could be <li>Services should do initial validation of the input. Not much validation was done here, but more could be
done. done.
</li> </li>
<li>Commands are reusable objects to perform recurring work.</li> <li>Commands are reusable objects to perform recurring work.</li>
<li>Queries and Services are privileged actions for which a user must have the privilege to perform the <li>Searches and Services are privileged actions for which a user must have the privilege to perform the
action. action.
</li> </li>
</ul> </ul>
<p>The book services are quite simple, but as more requirements arise, it should be easy to implement them in <p>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 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.</p> simply call the services, since the input is defined and validation is done there (i.e. NOT in the REST
API).</p>
<p>This concludes the CRUD of books.</p> <p>This concludes the CRUD of books.</p>
@ -614,7 +620,7 @@ public class RemoveBookService extends AbstractService&lt;StringServiceArgument,
s.parentNode.insertBefore(g, s); s.parentNode.insertBefore(g, s);
})(); })();
</script> </script>
<noscript><p><img src="http://piwik.eitchnet.ch/piwik.php?idsite=2" style="border:0;" alt=""/></p></noscript> <noscript><p><img src="http://piwik.eitchnet.ch/piwik.php?idsite=2" style="border:0;" alt="" /></p></noscript>
<!-- End Piwik Code --> <!-- End Piwik Code -->
</body> </body>