+ );
+ }
+});
+
+var App = React.createClass({
+ getInitialState: function() {
+ return {};
+ },
+ render: function() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+});
+
+ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById("content")
+);
diff --git a/lagom-cargotracker/front-end/app/controllers/Application.java b/lagom-cargotracker/front-end/app/controllers/Application.java
new file mode 100644
index 000000000000..ae5b4a517581
--- /dev/null
+++ b/lagom-cargotracker/front-end/app/controllers/Application.java
@@ -0,0 +1,15 @@
+package controllers;
+
+import play.mvc.*;
+
+public class Application extends Controller {
+
+ public Result index() {
+ return ok(views.html.index.render());
+ }
+
+ public Result userStream(String userId) {
+ return ok(views.html.index.render());
+ }
+
+}
diff --git a/lagom-cargotracker/front-end/app/views/index.scala.html b/lagom-cargotracker/front-end/app/views/index.scala.html
new file mode 100644
index 000000000000..8cb6e5e01b17
--- /dev/null
+++ b/lagom-cargotracker/front-end/app/views/index.scala.html
@@ -0,0 +1,18 @@
+
+
+ Cargotracker
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lagom-cargotracker/front-end/conf/application.conf b/lagom-cargotracker/front-end/conf/application.conf
new file mode 100644
index 000000000000..df2964514229
--- /dev/null
+++ b/lagom-cargotracker/front-end/conf/application.conf
@@ -0,0 +1,10 @@
+play.crypto.secret = "changeme"
+
+lagom.play {
+ service-name = "lagom-cargotracker-front-end"
+ acls = [
+ {
+ path-regex = "(?!/api/).*"
+ }
+ ]
+}
diff --git a/lagom-cargotracker/front-end/conf/routes b/lagom-cargotracker/front-end/conf/routes
new file mode 100644
index 000000000000..9e36edba5472
--- /dev/null
+++ b/lagom-cargotracker/front-end/conf/routes
@@ -0,0 +1,3 @@
+GET / controllers.Application.index()
+GET /addCargo controllers.Application.index()
+GET /assets/*file controllers.Assets.versioned(path = "/public", file)
diff --git a/lagom-cargotracker/project/build.properties b/lagom-cargotracker/project/build.properties
new file mode 100644
index 000000000000..43b8278c68cf
--- /dev/null
+++ b/lagom-cargotracker/project/build.properties
@@ -0,0 +1 @@
+sbt.version=0.13.11
diff --git a/lagom-cargotracker/project/plugins.sbt b/lagom-cargotracker/project/plugins.sbt
new file mode 100644
index 000000000000..1708db150ebc
--- /dev/null
+++ b/lagom-cargotracker/project/plugins.sbt
@@ -0,0 +1,3 @@
+addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.0.0-M1")
+addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")
+addSbtPlugin("com.github.ddispaltro" % "sbt-reactjs" % "0.5.2")
diff --git a/lagom-cargotracker/registration-api/src/main/java/sample/cargotracker/registration/api/AbstractCargo.java b/lagom-cargotracker/registration-api/src/main/java/sample/cargotracker/registration/api/AbstractCargo.java
new file mode 100644
index 000000000000..3c2e712f11c2
--- /dev/null
+++ b/lagom-cargotracker/registration-api/src/main/java/sample/cargotracker/registration/api/AbstractCargo.java
@@ -0,0 +1,26 @@
+package sample.cargotracker.registration.api;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
+import org.immutables.value.Value;
+
+@Value.Immutable
+@ImmutableStyle
+@JsonDeserialize(as = Cargo.class)
+public interface AbstractCargo {
+
+ @Value.Parameter
+ String getId();
+
+ @Value.Parameter
+ String getName();
+
+ @Value.Parameter
+ String getDescription();
+
+ @Value.Parameter
+ String getOwner();
+
+ @Value.Parameter
+ String getDestination();
+}
diff --git a/lagom-cargotracker/registration-api/src/main/java/sample/cargotracker/registration/api/RegistrationService.java b/lagom-cargotracker/registration-api/src/main/java/sample/cargotracker/registration/api/RegistrationService.java
new file mode 100644
index 000000000000..423d9cca03c6
--- /dev/null
+++ b/lagom-cargotracker/registration-api/src/main/java/sample/cargotracker/registration/api/RegistrationService.java
@@ -0,0 +1,53 @@
+package sample.cargotracker.registration.api;
+
+import static com.lightbend.lagom.javadsl.api.Service.*;
+
+import akka.Done;
+import akka.NotUsed;
+import akka.stream.javadsl.Source;
+
+import com.lightbend.lagom.javadsl.api.Descriptor;
+import com.lightbend.lagom.javadsl.api.Service;
+import com.lightbend.lagom.javadsl.api.ServiceCall;
+import com.lightbend.lagom.javadsl.api.transport.Method;
+import org.pcollections.PSequence;
+
+/**
+ * The registration service interface.
+ *
+ * This describes everything that Lagom needs to know about how to serve and consume the RegistrationService.
+ */
+public interface RegistrationService extends Service {
+
+ /**
+ * Example: curl -H "Content-Type: application/json" -X POST -d
+ * '{
+ * "cargo": {
+ * "id": 1,
+ * "name": "laptop",
+ * "description": "macbook",
+ * "owner": "Clark Kent",
+ * "destination": "Metropolis"
+ * }
+ * }' http://localhost:9000/api/registration
+ */
+ ServiceCall register();
+
+ ServiceCall> getLiveRegistrations();
+
+ ServiceCall> getAllRegistrations();
+
+ ServiceCall getRegistration();
+
+ @Override
+ default Descriptor descriptor() {
+ // @formatter:off
+ return named("registrationService").with(
+ restCall(Method.POST, "/api/registration", register()),
+ pathCall("/api/registration/live", getLiveRegistrations()),
+ restCall(Method.GET, "/api/registration/all", getAllRegistrations()),
+ restCall(Method.GET, "/api/registration/:id", getRegistration())
+ ).withAutoAcl(true);
+ // @formatter:on
+ }
+}
diff --git a/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/AbstractCargoState.java b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/AbstractCargoState.java
new file mode 100644
index 000000000000..497d1c5ae6c7
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/AbstractCargoState.java
@@ -0,0 +1,27 @@
+package sample.cargotracker.registration.impl;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.google.common.base.Preconditions;
+import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
+import com.lightbend.lagom.serialization.CompressedJsonable;
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import org.immutables.value.Value;
+import sample.cargotracker.registration.api.Cargo;
+
+/**
+ * The state for the cargo entity.
+ */
+@Value.Immutable
+@ImmutableStyle
+@JsonDeserialize(as = CargoState.class)
+public interface AbstractCargoState {
+
+
+ @Value.Parameter
+ Cargo getCargo();
+
+ @Value.Parameter
+ LocalDateTime getTimestamp();
+}
diff --git a/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/CargoEntity.java b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/CargoEntity.java
new file mode 100644
index 000000000000..fa9455b8c80a
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/CargoEntity.java
@@ -0,0 +1,86 @@
+package sample.cargotracker.registration.impl;
+
+import com.lightbend.lagom.javadsl.persistence.PersistentEntity;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import akka.Done;
+import sample.cargotracker.registration.api.Cargo;
+
+/**
+ * This is an event sourced entity. It has a state, {@link CargoState}, which stores what the registered cargo caries.
+ *
+ * Event sourced entities are interacted with by sending them commands. This entity supports one command,
+ * a {@link RegisterCargo} command, which is used for cargo registration.
+ *
+ * Commands get translated to events, and it's the events that get persisted by the entity. Each event
+ * will have an event handler registered for it, and an event handler simply applies an event to the
+ * current state. This will be done when the event is first created, and it will also be done when the
+ * entity is loaded from the database - each event will be replayed to recreate the state of the entity.
+ *
+ * This entity defines one event, the {@link CargoRegistered} event, which is emitted when a
+ * {@link RegisterCargo} command is received.
+ */
+public class CargoEntity extends PersistentEntity {
+
+ /**
+ * An entity can define different behaviours for different states, but it will always start with an
+ * initial behaviour. This entity only has one behaviour.
+ */
+ @Override
+ public Behavior initialBehavior(Optional snapshotState) {
+
+ /**
+ * Behaviour is defined using a behaviour builder. The behaviour builder starts with a state, if this
+ * entity supports snapshotting (an optimisation that allows the state itself to be persisted to combine
+ * many events into one), then the passed in snapshotState may have a value that can be used.
+ *
+ * Otherwise, the default state is to use a dummy Cargo with an id of empty string.
+ */
+ BehaviorBuilder b = newBehaviorBuilder(snapshotState.orElse(
+ CargoState.builder().cargo(
+ Cargo.builder()
+ .id("")
+ .description("")
+ .destination("")
+ .name("")
+ .owner("").build())
+ .timestamp(LocalDateTime.now()
+ ).build()));
+
+
+ // Command handlers are invoked for incoming messages (commands).
+ // A command handler must "return" the events to be persisted (if any).
+ b.setCommandHandler(RegisterCargo.class, (cmd, ctx) -> {
+ if (cmd.getCargo().getName() == null || cmd.getCargo().getName().equals("")) {
+ ctx.invalidCommand("Name must be defined");
+ return ctx.done();
+ }
+
+ final CargoRegistered cargoRegistered =
+ CargoRegistered.builder().cargo(cmd.getCargo()).id(entityId()).build();
+
+ return ctx.thenPersist(cargoRegistered, evt -> ctx.reply(Done.getInstance()));
+
+ });
+ /**
+ * Event handler for the CargoRegistered event.
+ */
+ b.setEventHandler(CargoRegistered.class,
+ // We simply update the current state to use the new cargo payload and update the timestamp
+ evt -> state()
+ .withCargo(evt.getCargo())
+ .withTimestamp(LocalDateTime.now())
+ );
+
+
+ // b.setReadOnlyCommandHandler()
+
+ /**
+ * We've defined all our behaviour, so build and return it.
+ */
+ return b.build();
+ }
+}
+
diff --git a/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/CargoEventProcessor.java b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/CargoEventProcessor.java
new file mode 100644
index 000000000000..470b182d6ad8
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/CargoEventProcessor.java
@@ -0,0 +1,150 @@
+package sample.cargotracker.registration.impl;
+
+import akka.Done;
+import com.datastax.driver.core.BoundStatement;
+import com.lightbend.lagom.javadsl.persistence.AggregateEventTag;
+import com.lightbend.lagom.javadsl.persistence.cassandra.CassandraReadSideProcessor;
+import com.lightbend.lagom.javadsl.persistence.cassandra.CassandraSession;
+
+import com.datastax.driver.core.PreparedStatement;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * Transform the Persistent Entity events into Cassandra database
+ * tables and records that can be queried from a service.
+ */
+public class CargoEventProcessor extends CassandraReadSideProcessor {
+
+ private final Logger log = LoggerFactory.getLogger(CargoEventProcessor.class);
+
+ @Override
+ public AggregateEventTag aggregateTag() {
+ return RegistrationEventTag.INSTANCE;
+ }
+
+
+ private PreparedStatement writeCargo = null; // initialized in prepare
+ private PreparedStatement writeOffset = null; // initialized in prepare
+
+ private void setWriteCargo(PreparedStatement writeCargo) {
+ this.writeCargo = writeCargo;
+ }
+
+ private void setWriteOffset(PreparedStatement writeOffset) {
+ this.writeOffset = writeOffset;
+ }
+
+ /**
+ * Prepare read-side table and statements
+ *
+ * @param session
+ * @return
+ */
+ @Override
+ public CompletionStage> prepare(CassandraSession session) {
+ // @formatter:off
+ return
+ prepareCreateTables(session).thenCompose(a ->
+ prepareWriteCargo(session).thenCompose(b ->
+ prepareWriteOffset(session).thenCompose(c ->
+ selectOffset(session))));
+ // @formatter:on
+ }
+
+ /**
+ * prepare read-side tables
+ *
+ * @param session
+ * @return
+ */
+ private CompletionStage prepareCreateTables(CassandraSession session) {
+ // @formatter:off
+ return session.executeCreateTable(
+ "CREATE TABLE IF NOT EXISTS cargo ("
+ + "cargoId text, name text, description text, owner text, destination text,"
+ + "PRIMARY KEY (cargoId, destination))")
+ .thenCompose(a -> session.executeCreateTable(
+ "CREATE TABLE IF NOT EXISTS cargo_offset ("
+ + "partition int, offset timeuuid, "
+ + "PRIMARY KEY (partition))"));
+ // @formatter:on
+ }
+
+ /**
+ * prepared statement for writing a Cargo object
+ *
+ * @param session
+ * @return
+ */
+ private CompletionStage prepareWriteCargo(CassandraSession session) {
+ return session.prepare("INSERT INTO cargo (cargoId, name, description, owner,destination) VALUES (?, ?,?,?,?)").thenApply(ps -> {
+ setWriteCargo(ps);
+ return Done.getInstance();
+ });
+ }
+
+ /**
+ * Prepared statement for the persistence offset
+ *
+ * @param session
+ * @return
+ */
+ private CompletionStage prepareWriteOffset(CassandraSession session) {
+ return session.prepare("INSERT INTO cargo_offset (partition, offset) VALUES (1, ?)").thenApply(ps -> {
+ setWriteOffset(ps);
+ return Done.getInstance();
+ });
+ }
+
+ /**
+ * Find persistence offset
+ *
+ * @param session
+ * @return
+ */
+ private CompletionStage> selectOffset(CassandraSession session) {
+ return session.selectOne("SELECT offset FROM cargo_offset")
+ .thenApply(
+ optionalRow -> optionalRow.map(r -> r.getUUID("offset")));
+ }
+
+ /**
+ * Bind the read side persistence to the CargoRegistered event
+ *
+ * @param builder
+ * @return
+ */
+ @Override
+ public EventHandlers defineEventHandlers(EventHandlersBuilder builder) {
+ builder.setEventHandler(CargoRegistered.class, this::processCargoRegistered);
+ return builder.build();
+ }
+
+ /**
+ * Write a persistent event into the read-side optimized database.
+ *
+ * @param @link{CargoRegistered}
+ * @param offset
+ * @return
+ */
+ private CompletionStage> processCargoRegistered(CargoRegistered event, UUID offset) {
+ BoundStatement bindWriteCargo = writeCargo.bind();
+ bindWriteCargo.setString("cargoId", event.getCargo().getId());
+ bindWriteCargo.setString("name", event.getCargo().getName());
+ bindWriteCargo.setString("description", event.getCargo().getDescription());
+ bindWriteCargo.setString("owner", event.getCargo().getOwner());
+ bindWriteCargo.setString("destination", event.getCargo().getDestination());
+ BoundStatement bindWriteOffset = writeOffset.bind(offset);
+ log.info("Persisted {}", event.getCargo().getId());
+ return completedStatements(Arrays.asList(bindWriteCargo, bindWriteOffset));
+ }
+
+}
diff --git a/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationCommand.java b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationCommand.java
new file mode 100644
index 000000000000..8abdf955f61d
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationCommand.java
@@ -0,0 +1,36 @@
+package sample.cargotracker.registration.impl;
+
+import akka.Done;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
+import com.lightbend.lagom.javadsl.persistence.PersistentEntity;
+import com.lightbend.lagom.serialization.CompressedJsonable;
+import com.lightbend.lagom.serialization.Jsonable;
+import org.immutables.value.Value;
+import sample.cargotracker.registration.api.Cargo;
+
+/**
+ * This interface defines all the commands that the Cargo entity supports.
+ *
+ * By convention, the commands should be inner classes of the interface, which makes it simple to get a
+ * complete picture of what commands an entity supports.
+ */
+public interface RegistrationCommand extends Jsonable {
+
+ /**
+ * A command to register cargo.
+ *
+ * It has a reply type of {@link akka.Done}, which is sent back to the caller when all the events
+ * emitted by this command are successfully persisted.
+ */
+ @Value.Immutable
+ @ImmutableStyle
+ @JsonDeserialize(as = RegisterCargo.class)
+ public interface AbstractRegisterCargo extends RegistrationCommand, CompressedJsonable,
+ PersistentEntity.ReplyType {
+
+ @Value.Parameter
+ Cargo getCargo();
+ }
+
+}
diff --git a/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationEvent.java b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationEvent.java
new file mode 100644
index 000000000000..f0c01d4eabad
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationEvent.java
@@ -0,0 +1,40 @@
+package sample.cargotracker.registration.impl;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
+import com.lightbend.lagom.javadsl.persistence.AggregateEvent;
+import com.lightbend.lagom.javadsl.persistence.AggregateEventTag;
+import com.lightbend.lagom.serialization.Jsonable;
+import org.immutables.value.Value;
+import org.immutables.value.Value.Immutable;
+import sample.cargotracker.registration.api.Cargo;
+
+/**
+ * This interface defines all the events that the Cargo entity supports.
+ *
+ * By convention, the events should be inner classes of the interface, which makes it simple to get a
+ * complete picture of what events an entity has.
+ */
+public interface RegistrationEvent extends Jsonable , AggregateEvent {
+
+ /**
+ * An event that represents a new cargo registration .
+ */
+ @Immutable
+ @ImmutableStyle
+ @JsonDeserialize(as = CargoRegistered.class)
+ interface AbstractCargoRegistered extends RegistrationEvent {
+
+ @Override
+ default public AggregateEventTag aggregateTag() {
+ return RegistrationEventTag.INSTANCE;
+ }
+
+
+ @Value.Parameter
+ String getId();
+
+ @Value.Parameter
+ Cargo getCargo();
+ }
+}
diff --git a/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationEventTag.java b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationEventTag.java
new file mode 100644
index 000000000000..85504cc9c911
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationEventTag.java
@@ -0,0 +1,13 @@
+package sample.cargotracker.registration.impl;
+
+import com.lightbend.lagom.javadsl.persistence.AggregateEventTag;
+
+/**
+ * Register a common event tag
+ */
+public class RegistrationEventTag {
+
+ public static final AggregateEventTag INSTANCE =
+ AggregateEventTag.of(RegistrationEvent.class);
+
+}
diff --git a/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationServiceImpl.java b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationServiceImpl.java
new file mode 100644
index 000000000000..77cb0db9fa82
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationServiceImpl.java
@@ -0,0 +1,138 @@
+package sample.cargotracker.registration.impl;
+
+import akka.Done;
+import akka.NotUsed;
+import akka.stream.javadsl.Source;
+import com.google.inject.Inject;
+import com.lightbend.lagom.javadsl.api.ServiceCall;
+import com.lightbend.lagom.javadsl.persistence.PersistentEntityRef;
+import com.lightbend.lagom.javadsl.persistence.PersistentEntityRegistry;
+import com.lightbend.lagom.javadsl.persistence.cassandra.CassandraReadSide;
+import com.lightbend.lagom.javadsl.persistence.cassandra.CassandraSession;
+import com.lightbend.lagom.javadsl.pubsub.PubSubRef;
+import com.lightbend.lagom.javadsl.pubsub.PubSubRegistry;
+import com.lightbend.lagom.javadsl.pubsub.TopicId;
+import org.pcollections.PSequence;
+import org.pcollections.TreePVector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sample.cargotracker.registration.api.Cargo;
+import sample.cargotracker.registration.api.RegistrationService;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.stream.Collectors;
+
+/**
+ * Implementation of the RegistrationService.
+ * Look at circuit breaker information: http://localhost:25003/_status/circuit-breaker/current
+ */
+public class RegistrationServiceImpl implements RegistrationService {
+
+ private final PersistentEntityRegistry persistentEntityRegistry;
+ private final PubSubRegistry topics;
+ private final CassandraSession db;
+ private final Logger log = LoggerFactory.getLogger(RegistrationServiceImpl.class);
+
+ /**
+ * Constructor with the relevant infrastructure elements injected.
+ *
+ * @param topics
+ * @param persistentEntityRegistry
+ * @param readSide
+ * @param db
+ */
+ @Inject
+ public RegistrationServiceImpl(PubSubRegistry topics, PersistentEntityRegistry persistentEntityRegistry, CassandraReadSide readSide,
+ CassandraSession db) {
+ this.persistentEntityRegistry = persistentEntityRegistry;
+ this.topics = topics;
+ this.db = db;
+ persistentEntityRegistry.register(CargoEntity.class);
+ readSide.register(CargoEventProcessor.class);
+ }
+
+ /**
+ * Register Cargo service call
+ *
+ * @return
+ */
+ @Override
+ public ServiceCall register() {
+ return (id, request) -> {
+ /* Publish received entity into topic named "Topic" */
+ PubSubRef topic = topics.refFor(TopicId.of(Cargo.class, "topic"));
+ topic.publish(request);
+ log.info("Cargo ID: {}.", request.getId());
+ /* Look up the Cargo entity for the given ID. */
+ PersistentEntityRef ref =
+ persistentEntityRegistry.refFor(CargoEntity.class, request.getId());
+ /* Tell the entity to use the Cargo information in the request. */
+ return ref.ask(RegisterCargo.of(request));
+ };
+ }
+
+ /**
+ * Get live registrations service call
+ *
+ * @return
+ */
+ @Override
+ public ServiceCall> getLiveRegistrations() {
+ return (id, req) -> {
+ PubSubRef topic = topics.refFor(TopicId.of(Cargo.class, "topic"));
+ return CompletableFuture.completedFuture(topic.subscriber());
+ };
+ }
+
+
+ /**
+ * Get all persisted Cargo services call
+ * Websockets capable
+ *
+ * @return
+ * @deprecated
+ */
+ public ServiceCall> getAllRegistrationsOld() {
+ log.info("Select all cargo .");
+ return (id, req) -> {
+ Source result = db.select(
+ "SELECT cargoId, name, description, owner, destination FROM cargo;").map(row ->
+ Cargo.of(row.getString("cargoId"),
+ row.getString("name"),
+ row.getString("description"),
+ row.getString("owner"),
+ row.getString("destination")));
+ return CompletableFuture.completedFuture(result);
+
+ };
+ }
+
+ /**
+ * Get all registered Cargo
+ *
+ * @return
+ */
+ @Override
+ public ServiceCall> getAllRegistrations() {
+ return (userId, req) -> {
+ CompletionStage> result = db.selectAll("SELECT cargoid, name, description, owner, destination FROM cargo")
+ .thenApply(rows -> {
+ List cargos = rows.stream().map(row -> Cargo.of(row.getString("cargoid"),
+ row.getString("name"),
+ row.getString("description"),
+ row.getString("owner"),
+ row.getString("destination"))).collect(Collectors.toList());
+ return TreePVector.from(cargos);
+ });
+ return result;
+ };
+ }
+
+ public ServiceCall getRegistration() {
+
+ //TODO Implement meaningful
+ return null;
+ }
+}
diff --git a/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationServiceModule.java b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationServiceModule.java
new file mode 100644
index 000000000000..bebc97db4cdd
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/java/sample/cargotracker/registration/impl/RegistrationServiceModule.java
@@ -0,0 +1,15 @@
+package sample.cargotracker.registration.impl;
+
+import com.google.inject.AbstractModule;
+import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport;
+import sample.cargotracker.registration.api.RegistrationService;
+
+/**
+ * The module that binds the RegistrationService so that it can be served.
+ */
+public class RegistrationServiceModule extends AbstractModule implements ServiceGuiceSupport {
+ @Override
+ protected void configure() {
+ bindServices(serviceBinding(RegistrationService.class, RegistrationServiceImpl.class));
+ }
+}
diff --git a/lagom-cargotracker/registration-impl/src/main/resources/application.conf b/lagom-cargotracker/registration-impl/src/main/resources/application.conf
new file mode 100644
index 000000000000..96cfb8673531
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/main/resources/application.conf
@@ -0,0 +1 @@
+play.modules.enabled += sample.cargotracker.registration.impl.RegistrationServiceModule
diff --git a/lagom-cargotracker/registration-impl/src/test/java/RegistrationServiceTest.java b/lagom-cargotracker/registration-impl/src/test/java/RegistrationServiceTest.java
new file mode 100644
index 000000000000..90b6b0340fb4
--- /dev/null
+++ b/lagom-cargotracker/registration-impl/src/test/java/RegistrationServiceTest.java
@@ -0,0 +1,22 @@
+
+import static com.lightbend.lagom.javadsl.testkit.ServiceTest.*;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+import sample.cargotracker.registration.api.RegistrationService;
+
+/**
+ * Created by myfear on 03/04/16.
+ */
+public class RegistrationServiceTest {
+
+ @Test
+ public void shouldSayHello() throws Exception {
+ withServer(defaultSetup(), server -> {
+ RegistrationService service = server.client(RegistrationService.class);
+
+ //TODO add meaningfull test
+ });
+ }
+
+}
diff --git a/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/ErrorHandler.java b/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/ErrorHandler.java
new file mode 100644
index 000000000000..6a3bcfe4cce4
--- /dev/null
+++ b/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/ErrorHandler.java
@@ -0,0 +1,69 @@
+package sample.cargotracker.shipping;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import play.Configuration;
+import play.Environment;
+import play.api.OptionalSourceMapper;
+import play.api.UsefulException;
+import play.api.routing.Router;
+import play.http.DefaultHttpErrorHandler;
+import play.libs.Json;
+import play.mvc.Http;
+import play.mvc.Result;
+import play.mvc.Results;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * Overrides the default HTTP error handler to return JSON
+ */
+public class ErrorHandler extends DefaultHttpErrorHandler {
+
+ @Inject
+ public ErrorHandler(Configuration configuration, Environment environment, OptionalSourceMapper sourceMapper, Provider routes) {
+ super(configuration, environment, sourceMapper, routes);
+ }
+
+ /**
+ * Invoked in dev mode when a server error occurs.
+ *
+ * @param request The request that triggered the error.
+ * @param exception The exception.
+ */
+ protected CompletionStage onDevServerError(Http.RequestHeader request, UsefulException exception) {
+
+ ObjectNode jsonError = Json.newObject();
+
+ final Throwable cause = exception.cause;
+ final String description = exception.description;
+ final String id = exception.id;
+ final String title = exception.title;
+
+ jsonError.put("description", description);
+ jsonError.put("title", title);
+ jsonError.put("id", id);
+ jsonError.put("message", exception.getMessage());
+ jsonError.set("cause", causesToJson(cause));
+
+ return CompletableFuture.completedFuture(Results.internalServerError(jsonError));
+ }
+
+ private ArrayNode causesToJson(Throwable throwable) {
+ ArrayNode causesNode = JsonNodeFactory.instance.arrayNode();
+
+ while (throwable != null) {
+ ObjectNode causeNode = causesNode.addObject();
+ causeNode.put("message", throwable.getMessage());
+ causeNode.put("type", throwable.getClass().getName());
+
+ throwable = throwable.getCause();
+ }
+
+ return causesNode;
+ }
+}
diff --git a/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/AbstractItinerary.java b/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/AbstractItinerary.java
new file mode 100644
index 000000000000..17bd377bc935
--- /dev/null
+++ b/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/AbstractItinerary.java
@@ -0,0 +1,32 @@
+package sample.cargotracker.shipping.api;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
+
+import java.util.List;
+
+import org.immutables.value.Value;
+
+import org.pcollections.PSequence;
+import sample.cargotracker.shipping.api.Leg;
+
+@Value.Immutable
+@ImmutableStyle
+@JsonDeserialize(as = Itinerary.class)
+public interface AbstractItinerary {
+
+ @Value.Parameter
+ String getId();
+
+ @Value.Parameter
+ String getCargoId();
+
+ @Value.Parameter
+ String getOrigin();
+
+ @Value.Parameter
+ String getDestination();
+
+ @Value.Parameter
+ PSequence getLegs();
+}
diff --git a/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/AbstractLeg.java b/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/AbstractLeg.java
new file mode 100644
index 000000000000..1ed653e17cb4
--- /dev/null
+++ b/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/AbstractLeg.java
@@ -0,0 +1,29 @@
+package sample.cargotracker.shipping.api;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
+
+import java.util.Date;
+
+import org.immutables.value.Value;
+
+@Value.Immutable
+@ImmutableStyle
+@JsonDeserialize(as = Leg.class)
+public interface AbstractLeg {
+
+ @Value.Parameter
+ String getId();
+
+ @Value.Parameter
+ String getCargoId();
+
+ @Value.Parameter
+ String getLocation();
+
+ @Value.Parameter
+ Date getArrivalTime();
+
+ @Value.Parameter
+ Date getDepartureTime();
+}
diff --git a/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/ShippingService.java b/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/ShippingService.java
new file mode 100644
index 000000000000..73cabf2b5a5d
--- /dev/null
+++ b/lagom-cargotracker/shipping-api/src/main/java/sample/cargotracker/shipping/api/ShippingService.java
@@ -0,0 +1,73 @@
+package sample.cargotracker.shipping.api;
+
+import static com.lightbend.lagom.javadsl.api.Service.named;
+import static com.lightbend.lagom.javadsl.api.Service.restCall;
+
+import akka.Done;
+import akka.NotUsed;
+import com.lightbend.lagom.javadsl.api.Descriptor;
+import com.lightbend.lagom.javadsl.api.Service;
+import com.lightbend.lagom.javadsl.api.ServiceCall;
+import com.lightbend.lagom.javadsl.api.transport.Method;
+
+/**
+ * The shipping service interface.
+ *
+ * This describes everything that Lagom needs to know about how to serve and consume the ShippingService.
+ */
+public interface ShippingService extends Service {
+
+ /**
+ * Example: curl -H "Content-Type: application/json" -X POST -d
+ * '{
+ * "itinerary": {
+ * "id": "1",
+ * "cargoId": "1",
+ * "origin": "Gothom City",
+ * "destination": "Metropolis",
+ * }
+ * }' http://localhost:9000/api/itinerary
+ */
+ ServiceCall createItinerary();
+
+ /**
+ * Adds a leg to an existing itinerary.
+ *
+ * The String here is the itinerary id.
+ *
+ *