I check in the trader-app demo
diff --git a/demos/jemo-trader-app/.gitignore b/demos/jemo-trader-app/.gitignore
new file mode 100644
index 0000000..29b636a
--- /dev/null
+++ b/demos/jemo-trader-app/.gitignore
@@ -0,0 +1,2 @@
+.idea
+*.iml
\ No newline at end of file
diff --git a/demos/jemo-trader-app/LICENSE b/demos/jemo-trader-app/LICENSE
new file mode 100644
index 0000000..d3087e4
--- /dev/null
+++ b/demos/jemo-trader-app/LICENSE
@@ -0,0 +1,277 @@
+Eclipse Public License - v 2.0
+
+ THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+ PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+ OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+ a) in the case of the initial Contributor, the initial content
+ Distributed under this Agreement, and
+
+ b) in the case of each subsequent Contributor:
+ i) changes to the Program, and
+ ii) additions to the Program;
+ where such changes and/or additions to the Program originate from
+ and are Distributed by that particular Contributor. A Contribution
+ "originates" from a Contributor if it was added to the Program by
+ such Contributor itself or anyone acting on such Contributor's behalf.
+ Contributions do not include changes or additions to the Program that
+ are not Modified Works.
+
+"Contributor" means any person or entity that Distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+"Program" means the Contributions Distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement
+or any Secondary License (as applicable), including Contributors.
+
+"Derivative Works" shall mean any work, whether in Source Code or other
+form, that is based on (or derived from) the Program and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship.
+
+"Modified Works" shall mean any work in Source Code or other form that
+results from an addition to, deletion from, or modification of the
+contents of the Program, including, for purposes of clarity any new file
+in Source Code form that contains any contents of the Program. Modified
+Works shall not include works that contain only declarations,
+interfaces, types, classes, structures, or files of the Program solely
+in each case in order to link to, bind by name, or subclass the Program
+or Modified Works thereof.
+
+"Distribute" means the acts of a) distributing or b) making available
+in any manner that enables the transfer of a copy.
+
+"Source Code" means the form of a Program preferred for making
+modifications, including but not limited to software source code,
+documentation source, and configuration files.
+
+"Secondary License" means either the GNU General Public License,
+Version 2.0, or any later versions of that license, including any
+exceptions or additional permissions as identified by the initial
+Contributor.
+
+2. GRANT OF RIGHTS
+
+ a) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare Derivative Works of, publicly display,
+ publicly perform, Distribute and sublicense the Contribution of such
+ Contributor, if any, and such Derivative Works.
+
+ b) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor,
+ if any, in Source Code or other form. This patent license shall
+ apply to the combination of the Contribution and the Program if, at
+ the time the Contribution is added by the Contributor, such addition
+ of the Contribution causes such combination to be covered by the
+ Licensed Patents. The patent license shall not apply to any other
+ combinations which include the Contribution. No hardware per se is
+ licensed hereunder.
+
+ c) Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity.
+ Each Contributor disclaims any liability to Recipient for claims
+ brought by any other entity based on infringement of intellectual
+ property rights or otherwise. As a condition to exercising the
+ rights and licenses granted hereunder, each Recipient hereby
+ assumes sole responsibility to secure any other intellectual
+ property rights needed, if any. For example, if a third party
+ patent license is required to allow Recipient to Distribute the
+ Program, it is Recipient's responsibility to acquire that license
+ before distributing the Program.
+
+ d) Each Contributor represents that to its knowledge it has
+ sufficient copyright rights in its Contribution, if any, to grant
+ the copyright license set forth in this Agreement.
+
+ e) Notwithstanding the terms of any Secondary License, no
+ Contributor makes additional grants to any Recipient (other than
+ those set forth in this Agreement) as a result of such Recipient's
+ receipt of the Program under the terms of a Secondary License
+ (if permitted under the terms of Section 3).
+
+3. REQUIREMENTS
+
+3.1 If a Contributor Distributes the Program in any form, then:
+
+ a) the Program must also be made available as Source Code, in
+ accordance with section 3.2, and the Contributor must accompany
+ the Program with a statement that the Source Code for the Program
+ is available under this Agreement, and informs Recipients how to
+ obtain it in a reasonable manner on or through a medium customarily
+ used for software exchange; and
+
+ b) the Contributor may Distribute the Program under a license
+ different than this Agreement, provided that such license:
+ i) effectively disclaims on behalf of all other Contributors all
+ warranties and conditions, express and implied, including
+ warranties or conditions of title and non-infringement, and
+ implied warranties or conditions of merchantability and fitness
+ for a particular purpose;
+
+ ii) effectively excludes on behalf of all other Contributors all
+ liability for damages, including direct, indirect, special,
+ incidental and consequential damages, such as lost profits;
+
+ iii) does not attempt to limit or alter the recipients' rights
+ in the Source Code under section 3.2; and
+
+ iv) requires any subsequent distribution of the Program by any
+ party to be under a license that satisfies the requirements
+ of this section 3.
+
+3.2 When the Program is Distributed as Source Code:
+
+ a) it must be made available under this Agreement, or if the
+ Program (i) is combined with other material in a separate file or
+ files made available under a Secondary License, and (ii) the initial
+ Contributor attached to the Source Code the notice described in
+ Exhibit A of this Agreement, then the Program may be made available
+ under the terms of such Secondary Licenses, and
+
+ b) a copy of this Agreement must be included with each copy of
+ the Program.
+
+3.3 Contributors may not remove or alter any copyright, patent,
+trademark, attribution notices, disclaimers of warranty, or limitations
+of liability ("notices") contained within the Program from any copy of
+the Program which they Distribute, provided that Contributors may add
+their own appropriate notices.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program,
+the Contributor who includes the Program in a commercial product
+offering should do so in a manner which does not create potential
+liability for other Contributors. Therefore, if a Contributor includes
+the Program in a commercial product offering, such Contributor
+("Commercial Contributor") hereby agrees to defend and indemnify every
+other Contributor ("Indemnified Contributor") against any losses,
+damages and costs (collectively "Losses") arising from claims, lawsuits
+and other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such
+Commercial Contributor in connection with its distribution of the Program
+in a commercial product offering. The obligations in this section do not
+apply to any claims or Losses relating to any actual or alleged
+intellectual property infringement. In order to qualify, an Indemnified
+Contributor must: a) promptly notify the Commercial Contributor in
+writing of such claim, and b) allow the Commercial Contributor to control,
+and cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may
+participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those performance
+claims and warranties, and if a court requires any other Contributor to
+pay any damages as a result, the Commercial Contributor must pay
+those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
+TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+PURPOSE. Each Recipient is solely responsible for determining the
+appropriateness of using and distributing the Program and assumes all
+risks associated with its exercise of rights under this Agreement,
+including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs
+or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
+SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other software
+or hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+but in order to avoid inconsistency the Agreement is copyrighted and
+may only be modified in the following manner. The Agreement Steward
+reserves the right to publish new versions (including revisions) of
+this Agreement from time to time. No one other than the Agreement
+Steward has the right to modify this Agreement. The Eclipse Foundation
+is the initial Agreement Steward. The Eclipse Foundation may assign the
+responsibility to serve as the Agreement Steward to a suitable separate
+entity. Each new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+Distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is published,
+Contributor may elect to Distribute the Program (including its
+Contributions) under the new version.
+
+Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+receives no rights or licenses to the intellectual property of any
+Contributor under this Agreement, whether expressly, by implication,
+estoppel or otherwise. All rights in the Program not expressly granted
+under this Agreement are reserved. Nothing in this Agreement is intended
+to be enforceable by any entity that is not a Contributor or Recipient.
+No third-party beneficiary rights are created under this Agreement.
+
+Exhibit A - Form of Secondary Licenses Notice
+
+"This Source Code may also be made available under the following
+Secondary Licenses when the conditions for such availability set forth
+in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+version(s), and exceptions or additional permissions here}."
+
+ Simply including a copy of this Agreement, including this Exhibit A
+ is not sufficient to license the Source Code under Secondary Licenses.
+
+ If it is not possible or desirable to put the notice in a particular
+ file, then You may include the notice in a location (such as a LICENSE
+ file in a relevant directory) where a recipient would be likely to
+ look for such a notice.
+
+ You may add additional accurate notices of copyright ownership.
diff --git a/demos/jemo-trader-app/README.md b/demos/jemo-trader-app/README.md
new file mode 100644
index 0000000..1aae562
--- /dev/null
+++ b/demos/jemo-trader-app/README.md
@@ -0,0 +1,2 @@
+# eclipse-jemo-trader-app
+Demo application showing how Eclipse Jemo can be used to implement a simplistic Market simulation
diff --git a/demos/jemo-trader-app/pom.xml b/demos/jemo-trader-app/pom.xml
new file mode 100644
index 0000000..f149701
--- /dev/null
+++ b/demos/jemo-trader-app/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.eclipse.jemo</groupId>
+ <artifactId>jemo-trader-app</artifactId>
+ <version>1.0</version>
+ <packaging>jar</packaging>
+
+ <name>Jemo Trader App</name>
+ <description>This is a demo project showing how to use Eclipse Jemo development patterns to create an application.
+ As an example we model a Market with traders and stocks.
+ </description>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ </properties>
+
+ <repositories>
+ <repository>
+ <id>jemo-local</id>
+ <url>http://localhost/jemo</url>
+ </repository>
+ </repositories>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jemo</groupId>
+ <artifactId>module-api</artifactId>
+ <version>1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>jar-with-dependencies</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>eclipse-jemo-maven-plugin</artifactId>
+ <version>1.0</version>
+ <executions>
+ <execution>
+ <phase>deploy</phase>
+ <goals>
+ <goal>deploy</goal>
+ </goals>
+ <configuration>
+ <outputJar>${project.build.finalName}-jar-with-dependencies</outputJar>
+ <!-- Set the Jemo plugin id to upload here -->
+ <id>1</id>
+ <username>[JEMO_USER]</username>
+ <password>[JEMO_PASSWORD]</password>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Market.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Market.java
new file mode 100644
index 0000000..d93f43d
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Market.java
@@ -0,0 +1,197 @@
+package org.eclipse.jemo.tutorial.market;
+
+import org.eclipse.jemo.api.WebServiceModule;
+import org.eclipse.jemo.internal.model.JemoMessage;
+import org.eclipse.jemo.sys.internal.Util;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.IntStream;
+
+import static java.util.logging.Level.INFO;
+import static org.eclipse.jemo.tutorial.market.MarketMatcher.TRADER_ID;
+
+/**
+ * Demonstration of the Web service Jemo development pattern.
+ * Implements a REST API for modeling a market with traders and stocks.
+ *
+ * @author Yannis Theocharis
+ */
+public class Market implements WebServiceModule {
+
+ private static final Pattern TRADERS_PATTERN = Pattern.compile("/market/traders");
+ private static final Pattern ONE_TRADER_PATTERN = Pattern.compile("/market/traders/(\\d+)");
+ private static final Pattern STOCKS_PATTERN = Pattern.compile("/market/stocks");
+ private static final Pattern ONE_STOCK_PATTERN = Pattern.compile("/market/stocks/(\\d+)");
+ public static int CURRENT_STOCK_ID = 21;
+
+ public static TraderRepository TRADER_REPOSITORY;
+ public static StockRepository STOCK_REPOSITORY;
+
+ @Override
+ public void construct(Logger logger, String name, int id, double version) {
+ WebServiceModule.super.construct(logger, name, id, version);
+ TRADER_REPOSITORY = new TraderRepository(getRuntime());
+ STOCK_REPOSITORY = new StockRepository(getRuntime());
+ STOCK_REPOSITORY.findMaxId().ifPresent(maxId -> CURRENT_STOCK_ID = maxId + 1);
+ }
+
+ @Override
+ public void installed() {
+ log(INFO, "Installed phase. Initializing the database...");
+ TRADER_REPOSITORY.init();
+ STOCK_REPOSITORY.init();
+
+ // Create 20 stocks.
+ final Stock[] stocks = IntStream.range(1, CURRENT_STOCK_ID)
+ .mapToObj(id -> new Stock(String.valueOf(id), 100f))
+ .toArray(Stock[]::new);
+ STOCK_REPOSITORY.save(stocks);
+
+ // Create 10 traders and assign 2 stock to each one of them.
+ final Trader[] traders = IntStream.range(1, 11)
+ .mapToObj(id -> {
+ final int stockIndex = 2 * (id - 1);
+ final Trader trader = new Trader(String.valueOf(id), 1000f).acquire(stocks[stockIndex]);
+ if (stockIndex + 1 < CURRENT_STOCK_ID) {
+ trader.acquire(stocks[stockIndex + 1]);
+ }
+ return trader;
+ })
+ .toArray(Trader[]::new);
+ TRADER_REPOSITORY.save(traders);
+ }
+
+ @Override
+ public String getBasePath() {
+ return "/market";
+ }
+
+ @Override
+ public void process(HttpServletRequest request, HttpServletResponse response) throws Throwable {
+ final String endpoint = request.getRequestURI().substring(request.getRequestURI().indexOf(getBasePath()));
+ switch (request.getMethod()) {
+ case "GET":
+ Matcher matcher;
+ log(INFO, "endpoint: " + endpoint);
+ if ((matcher = ONE_TRADER_PATTERN.matcher(endpoint)).find()) {
+ getTrader(matcher.group(1), response);
+ } else if (TRADERS_PATTERN.matcher(endpoint).find()) {
+ getAllTraders(response);
+ } else if ((matcher = ONE_STOCK_PATTERN.matcher(endpoint)).find()) {
+ getStock(matcher.group(1), response);
+ } else if (STOCKS_PATTERN.matcher(endpoint).find()) {
+ getAllStocks(response);
+ } else {
+ response.sendError(400);
+ }
+ break;
+
+ case "PUT":
+ if ((matcher = ONE_TRADER_PATTERN.matcher(endpoint)).find()) {
+ updateTrader(matcher.group(1), request, response);
+ } else {
+ response.sendError(400);
+ }
+ break;
+
+ case "PATCH":
+ if ((matcher = ONE_TRADER_PATTERN.matcher(endpoint)).find()) {
+ partialUpdateTrader(matcher.group(1), request, response);
+ } else {
+ response.sendError(400);
+ }
+ break;
+ default:
+ response.sendError(400);
+ }
+ }
+
+ private void getAllTraders(HttpServletResponse response) throws IOException {
+ respondWithJson(200, response, TRADER_REPOSITORY.findAll());
+ }
+
+ private void getTrader(String id, HttpServletResponse response) throws IOException {
+ final Optional<Trader> trader = TRADER_REPOSITORY.findById(id);
+ if (trader.isPresent()) {
+ respondWithJson(200, response, trader.get());
+ } else {
+ respondWithJson(404, response, null);
+ }
+ }
+
+ private void updateTrader(String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
+ log(INFO, "updateTrader");
+ final Optional<Trader> trader = TRADER_REPOSITORY.findById(id);
+ if (trader.isPresent()) {
+ final Trader newState = Util.fromJSONString(Trader.class, Util.toString(request.getInputStream()));
+ final boolean differsInTargetValue = newState.differsInTargetValue(trader.get());
+ TRADER_REPOSITORY.save(newState);
+ if (differsInTargetValue) {
+ triggerEvent(newState.getId());
+ }
+ respondWithJson(200, response, newState);
+ } else {
+ respondWithJson(404, response, null);
+ }
+ }
+
+ private void partialUpdateTrader(String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
+ log(INFO, "partialUpdateTrader");
+ final Optional<Trader> optionalTrader = TRADER_REPOSITORY.findById(id);
+ if (optionalTrader.isPresent()) {
+ final Trader trader = optionalTrader.get();
+ final Trader newState = Util.fromJSONString(Trader.class, Util.toString(request.getInputStream()));
+ final boolean differsInTargetValue = newState.differsInTargetValue(trader);
+ trader.setStockIdToBuyTargetValue(newState.getStockIdToBuyTargetValue());
+ trader.setStockIdToSellTargetValue(newState.getStockIdToSellTargetValue());
+ TRADER_REPOSITORY.save(trader);
+ if (differsInTargetValue) {
+ triggerEvent(trader.getId());
+ }
+ respondWithJson(200, response, trader);
+ } else {
+ respondWithJson(404, response, null);
+ }
+ }
+
+ private void triggerEvent(String traderId) {
+ JemoMessage msg = new JemoMessage();
+ msg.setModuleClass(MarketMatcher.class.getName());
+ msg.setId("1");
+ msg.setPluginId(1);
+ msg.getAttributes().put(TRADER_ID, traderId);
+ msg.send(JemoMessage.LOCATION_LOCALLY);
+ }
+
+ private void getAllStocks(HttpServletResponse response) throws IOException {
+ respondWithJson(200, response, STOCK_REPOSITORY.findAll());
+ }
+
+ private void getStock(String id, HttpServletResponse response) throws IOException {
+ final Optional<Stock> stock = STOCK_REPOSITORY.findById(id);
+ if (stock.isPresent()) {
+ respondWithJson(200, response, stock.get());
+ } else {
+ respondWithJson(404, response, null);
+ }
+ }
+
+ private static void respondWithJson(int statusCode, HttpServletResponse response, Object obj) throws IOException {
+ response.setStatus(statusCode);
+ response.setContentType("application/json");
+ final OutputStream out = response.getOutputStream();
+ final String json = Util.toJSONString(obj);
+ out.write(json.getBytes(StandardCharsets.UTF_8));
+ out.flush();
+ out.close();
+ }
+
+}
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketIPO.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketIPO.java
new file mode 100644
index 0000000..a7448c2
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketIPO.java
@@ -0,0 +1,39 @@
+package org.eclipse.jemo.tutorial.market;
+
+import org.eclipse.jemo.api.BatchModule;
+import org.eclipse.jemo.api.Frequency;
+import org.eclipse.jemo.api.ModuleLimit;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.logging.Level.INFO;
+import static org.eclipse.jemo.tutorial.market.Market.*;
+
+/**
+ * Simulates a simplistic IPO, where 1 new stock is added to the market and owned by an existing trader.
+ * This is repeated every 1 minute that the processBatch is called by Jemo.
+ *
+ * @author Yannis Theocharis
+ */
+public class MarketIPO implements BatchModule {
+ private static Random RANDOM = new Random();
+
+ @Override
+ public void processBatch(String location, boolean isCloudLocation) throws Throwable {
+ final Trader trader = TRADER_REPOSITORY.findById(String.valueOf(RANDOM.nextInt(10) + 1)).get();
+ final Stock stock = new Stock(String.valueOf(CURRENT_STOCK_ID++), 100f);
+ log(INFO, String.format("An IPO occurred - Trader [%s] owns the stock [%s]", trader.getId(), stock.getId()));
+ trader.acquire(stock);
+ TRADER_REPOSITORY.save(trader);
+ STOCK_REPOSITORY.save(stock);
+ }
+
+ @Override
+ public ModuleLimit getLimits() {
+ return ModuleLimit.newInstance()
+ .setMaxActiveBatchesPerGSM(1)
+ .setBatchFrequency(Frequency.of(TimeUnit.SECONDS, 30))
+ .build();
+ }
+}
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketMatcher.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketMatcher.java
new file mode 100644
index 0000000..a4b63a9
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketMatcher.java
@@ -0,0 +1,76 @@
+package org.eclipse.jemo.tutorial.market;
+
+import org.eclipse.jemo.api.EventModule;
+import org.eclipse.jemo.internal.model.JemoMessage;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static java.util.logging.Level.INFO;
+import static org.eclipse.jemo.tutorial.market.Market.STOCK_REPOSITORY;
+import static org.eclipse.jemo.tutorial.market.Market.TRADER_REPOSITORY;
+import static org.eclipse.jemo.tutorial.market.MarketWatch.TRANSACTIONS;
+
+/**
+ * Consumes Jemo Messages produced when a trader changes his target values.
+ * Attempts to match these target values with other traders target values.
+ *
+ * @author Yannis Theocharis
+ */
+public class MarketMatcher implements EventModule {
+
+ public static final String TRADER_ID = "TRADER_ID";
+
+ @Override
+ public JemoMessage process(JemoMessage msg) throws IOException {
+ log(INFO, "Consuming message...");
+
+ final Trader sourceTrader = TRADER_REPOSITORY.findById((String) msg.getAttributes().get(TRADER_ID)).get();
+
+ final List<Trader> traders = TRADER_REPOSITORY.findAll();
+ Collections.shuffle(traders);
+
+ for (Map.Entry<String, Float> entry : sourceTrader.getStockIdToBuyTargetValue().entrySet()) {
+ final Optional<Trader> targetTrader = traders.stream()
+ .filter(trader -> trader != sourceTrader && (trader.sellTargetValue(entry.getKey()) != null && trader.sellTargetValue(entry.getKey()) <= entry.getValue()))
+ .findFirst();
+ if (targetTrader.isPresent()) {
+ final Trader seller = targetTrader.get();
+ final Float value = seller.sellTargetValue(entry.getKey());
+ trade(sourceTrader, seller, entry.getKey(), value);
+ break;
+ }
+ }
+
+ for (Map.Entry<String, Float> entry : sourceTrader.getStockIdToSellTargetValue().entrySet()) {
+ final Optional<Trader> targetTrader = traders.stream()
+ .filter(trader -> trader != sourceTrader && trader.buyTargetValue(entry.getKey()) != null && trader.buyTargetValue(entry.getKey()) >= entry.getValue())
+ .findFirst();
+ if (targetTrader.isPresent()) {
+ final Trader buyer = targetTrader.get();
+ final Float value = buyer.buyTargetValue(entry.getKey());
+ trade(buyer, sourceTrader, entry.getKey(), value);
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ private void trade(Trader buyer, Trader seller, String stockId, Float value) {
+ log(INFO, String.format("Matching buyer [%s] with seller [%s] for stock [%s] and value [%s]...", buyer.getId(), seller.getId(), stockId, value));
+
+ final Stock stock = STOCK_REPOSITORY.findById(stockId).get();
+ buyer.buy(stock, value);
+ seller.sell(stock, value);
+ stock.setValue(value);
+ TRADER_REPOSITORY.save(buyer, seller);
+ STOCK_REPOSITORY.save(stock);
+ TRANSACTIONS.add(new Transaction(LocalDateTime.now(), buyer.getId(), seller.getId(), stockId, value));
+ }
+
+}
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketWatch.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketWatch.java
new file mode 100644
index 0000000..86bf410
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/MarketWatch.java
@@ -0,0 +1,50 @@
+package org.eclipse.jemo.tutorial.market;
+
+import org.eclipse.jemo.api.FixedModule;
+import org.eclipse.jemo.api.ModuleLimit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static java.util.logging.Level.INFO;
+
+/**
+ * Declares a fixed process that logs the trasnactions processed by this instance the last 30 seconds.
+ *
+ * @author Yannis Theocharis
+ */
+public class MarketWatch implements FixedModule {
+
+ public static final List<Transaction> TRANSACTIONS = new ArrayList<>();
+ public AtomicBoolean running = new AtomicBoolean(false);
+
+ @Override
+ public void start() {
+ running.set(true);
+ }
+
+ @Override
+ public void stop() {
+ running.set(false);
+ }
+
+ @Override
+ public void processFixed(String location, String instanceId) throws Throwable {
+ log(INFO, String.format("Process fixed Location [%s] - instance [%s]: [%s]", location, instanceId, TRANSACTIONS));
+ while (running.get()) {
+ // We could send the transactions to a consumer. For demo purposes we just log them.
+ TRANSACTIONS.forEach(txn -> log(INFO, String.format("Txn processed in Location [%s] - instance [%s]: [%s]", location, instanceId, txn)));
+ TRANSACTIONS.clear();
+ TimeUnit.SECONDS.sleep(30);
+ }
+ }
+
+ @Override
+ public ModuleLimit getLimits() {
+ return ModuleLimit.newInstance()
+ .setMaxActiveFixedPerInstance(1)
+ .build();
+ }
+}
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Stock.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Stock.java
new file mode 100644
index 0000000..1c3abc3
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Stock.java
@@ -0,0 +1,58 @@
+package org.eclipse.jemo.tutorial.market;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.eclipse.jemo.internal.model.SystemDBObject;
+
+import java.util.Objects;
+
+/**
+ * Models a stock.
+ *
+ * @author Yannis Theocharis
+ */
+public class Stock implements SystemDBObject {
+
+ @JsonProperty
+ private String id;
+
+ @JsonProperty
+ private float value;
+
+ public Stock() {
+ }
+
+ public Stock(String id, float value) {
+ this.id = id;
+ this.value = value;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setValue(float value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Stock stock = (Stock) o;
+ return Objects.equals(id, stock.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public String toString() {
+ return "Stock{" +
+ "id='" + id + '\'' +
+ ", value=" + value +
+ '}';
+ }
+}
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/StockRepository.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/StockRepository.java
new file mode 100644
index 0000000..2a3ae7b
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/StockRepository.java
@@ -0,0 +1,57 @@
+package org.eclipse.jemo.tutorial.market;
+
+import org.eclipse.jemo.internal.model.CloudRuntime;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
+
+/**
+ * Models a stock NoSQL repository.
+ * Delegates method calls to the CloudRuntime implementation.
+ *
+ * @author Yannis Theocharis
+ */
+public class StockRepository {
+
+ private static final String TABLE_NAME = "stocks";
+
+ private final CloudRuntime runtime;
+
+ public StockRepository(CloudRuntime runtime) {
+ this.runtime = runtime;
+ }
+
+ public void init() {
+ if (runtime.hasNoSQLTable(TABLE_NAME)) {
+ runtime.dropNoSQLTable(TABLE_NAME);
+ }
+ runtime.createNoSQLTable(TABLE_NAME);
+
+ }
+
+ public List<Stock> findAll() {
+ return runtime.listNoSQL(TABLE_NAME, Stock.class);
+ }
+
+ public Optional<Stock> findById(String id) {
+ try {
+ return Optional.ofNullable(runtime.getNoSQL(TABLE_NAME, id, Stock.class));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void save(Stock... stocks) {
+ runtime.saveNoSQL(TABLE_NAME, stocks);
+ }
+
+ public OptionalInt findMaxId() {
+ if (runtime.hasNoSQLTable(TABLE_NAME)) {
+ return findAll().stream().mapToInt(stock -> Integer.parseInt(stock.getId())).max();
+ } else {
+ return OptionalInt.empty();
+ }
+ }
+}
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Trader.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Trader.java
new file mode 100644
index 0000000..8a48c35
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Trader.java
@@ -0,0 +1,128 @@
+package org.eclipse.jemo.tutorial.market;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.eclipse.jemo.internal.model.SystemDBObject;
+
+import java.util.*;
+
+/**
+ * Models a trader.
+ * Traders can have stocks and can set buy and sell target values for stocks.
+ *
+ * @author Yannis Theocharis
+ */
+public class Trader implements SystemDBObject {
+
+ @JsonProperty
+ private String id;
+
+ @JsonProperty
+ private float accountBalance;
+
+ @JsonProperty
+ private Set<Stock> stocks;
+
+ @JsonProperty
+ private Map<String, Float> stockIdToBuyTargetValue;
+
+ @JsonProperty
+ private Map<String, Float> stockIdToSellTargetValue;
+
+ public Trader() {
+ }
+
+ public Trader(String id, float accountBalance) {
+ this.id = id;
+ this.accountBalance = accountBalance;
+ stocks = new HashSet<>();
+ stockIdToBuyTargetValue = new HashMap<>();
+ stockIdToSellTargetValue = new HashMap<>();
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public Trader acquire(Stock stock) {
+ stocks.add(stock);
+ return this;
+ }
+
+ public boolean buy(Stock stock, float value) {
+ if (accountBalance >= value) {
+ accountBalance -= value;
+ stocks.add(stock);
+ stockIdToBuyTargetValue.remove(stock.getId());
+ return true;
+ }
+ return false;
+ }
+
+ public boolean sell(Stock stock, float value) {
+ if (stocks.contains(stock)) {
+ accountBalance += value;
+ stocks.remove(stock);
+ stockIdToSellTargetValue.remove(stock.getId());
+ return true;
+ }
+ return false;
+ }
+
+ public boolean differsInTargetValue(Trader other) {
+ return !this.stockIdToBuyTargetValue.equals(other.stockIdToBuyTargetValue) ||
+ !this.stockIdToSellTargetValue.equals(other.stockIdToSellTargetValue);
+ }
+
+ public Map<String, Float> getStockIdToBuyTargetValue() {
+ return stockIdToBuyTargetValue;
+ }
+
+ public void setStockIdToBuyTargetValue(Map<String, Float> stockIdToBuyTargetValue) {
+ this.stockIdToBuyTargetValue = stockIdToBuyTargetValue;
+ }
+
+ public Map<String, Float> getStockIdToSellTargetValue() {
+ return stockIdToSellTargetValue;
+ }
+
+ public void setStockIdToSellTargetValue(Map<String, Float> stockIdToSellTargetValue) {
+ this.stockIdToSellTargetValue = stockIdToSellTargetValue;
+ }
+
+ public Float buyTargetValue(String stockId) {
+ return stockIdToBuyTargetValue.get(stockId);
+ }
+
+ public Float sellTargetValue(String stockId) {
+ return stockIdToSellTargetValue.get(stockId);
+ }
+
+ @Override
+ public String toString() {
+ return "Trader{" +
+ "id='" + id + '\'' +
+ ", accountBalance=" + accountBalance +
+ ", stocks=" + stocks +
+ ", stockIdToBuyTargetValue=" + stockIdToBuyTargetValue +
+ ", stockIdToSellTargetValue=" + stockIdToSellTargetValue +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Trader trader = (Trader) o;
+ return Float.compare(trader.accountBalance, accountBalance) == 0 &&
+ Objects.equals(id, trader.id) &&
+ Objects.equals(stocks, trader.stocks) &&
+ Objects.equals(stockIdToBuyTargetValue, trader.stockIdToBuyTargetValue) &&
+ Objects.equals(stockIdToSellTargetValue, trader.stockIdToSellTargetValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, accountBalance, stocks, stockIdToBuyTargetValue, stockIdToSellTargetValue);
+ }
+}
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/TraderRepository.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/TraderRepository.java
new file mode 100644
index 0000000..47dbb21
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/TraderRepository.java
@@ -0,0 +1,46 @@
+package org.eclipse.jemo.tutorial.market;
+
+import org.eclipse.jemo.internal.model.CloudRuntime;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Models a trader NoSQL repository.
+ * Delegates method calls to the CloudRuntime implementation.
+ *
+ * @author Yannis Theocharis
+ */
+public class TraderRepository {
+
+ private static final String TABLE_NAME = "traders";
+
+ private final CloudRuntime runtime;
+
+ public TraderRepository(CloudRuntime runtime) {
+ this.runtime = runtime;
+ }
+
+ public void init() {
+ if (!runtime.hasNoSQLTable(TABLE_NAME)) {
+ runtime.createNoSQLTable(TABLE_NAME);
+ }
+ }
+
+ public List<Trader> findAll() {
+ return runtime.listNoSQL(TABLE_NAME, Trader.class);
+ }
+
+ public Optional<Trader> findById(String id) throws IOException {
+ return Optional.ofNullable(runtime.getNoSQL(TABLE_NAME, id, Trader.class));
+ }
+
+ public void save(Trader... trader) {
+ runtime.saveNoSQL(TABLE_NAME, trader);
+ }
+
+ public void deleteAll() {
+ runtime.deleteNoSQL(TABLE_NAME, runtime.listNoSQL(TABLE_NAME, Trader.class).toArray(new Trader[0]));
+ }
+}
diff --git a/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Transaction.java b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Transaction.java
new file mode 100644
index 0000000..899c319
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/java/org/eclipse/jemo/tutorial/market/Transaction.java
@@ -0,0 +1,48 @@
+package org.eclipse.jemo.tutorial.market;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author Yannis Theocharis
+ */
+public class Transaction {
+
+ @JsonProperty
+ private LocalDateTime time;
+
+ @JsonProperty
+ private String buyer;
+
+ @JsonProperty
+ private String seller;
+
+ @JsonProperty
+ private String stock;
+
+ @JsonProperty
+ private Float value;
+
+ public Transaction() {
+ }
+
+ public Transaction(LocalDateTime time, String buyer, String seller, String stock, Float value) {
+ this.time = time;
+ this.buyer = buyer;
+ this.seller = seller;
+ this.stock = stock;
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return "Transaction{" +
+ "time=" + time +
+ ", buyer='" + buyer + '\'' +
+ ", seller='" + seller + '\'' +
+ ", stock='" + stock + '\'' +
+ ", value=" + value +
+ '}';
+ }
+}
diff --git a/demos/jemo-trader-app/src/main/resources/apidocs/index.html b/demos/jemo-trader-app/src/main/resources/apidocs/index.html
new file mode 100644
index 0000000..5bb65e5
--- /dev/null
+++ b/demos/jemo-trader-app/src/main/resources/apidocs/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Jemo demo trader App</title>
+</head>
+<body>
+Documentation
+</body>
+</html>
\ No newline at end of file