Skip to content
This repository has been archived by the owner on Feb 1, 2020. It is now read-only.

Commit

Permalink
REST and Bootstrap Components demo
Browse files Browse the repository at this point in the history
  • Loading branch information
Starzu committed Jul 11, 2016
1 parent cfd7f98 commit d745c2a
Show file tree
Hide file tree
Showing 47 changed files with 1,709 additions and 2 deletions.
9 changes: 7 additions & 2 deletions README.md
Expand Up @@ -4,8 +4,13 @@ Collection of demo applications based on Udash.

## Todo

Demo based on [TodoMVC](http://todomvc.com/). This is frontend-only project.
A demo based on [TodoMVC](http://todomvc.com/). This is frontend-only project.

## Todo RPC

Demo based on [TodoMVC](http://todomvc.com/) with server-side storage using the Udash RPC system.
A demo based on [TodoMVC](http://todomvc.com/) with server-side storage using the Udash RPC system.

## Udash REST with Bootstrap Components

A demo presenting usage of the Udash REST module. It serves REST API with Spray.io in the backend application and uses it through type safe Udash REST interface in the frontend.
This demo uses the Udash Bootstrap Components library.
130 changes: 130 additions & 0 deletions rest-spray-io/.gitignore
@@ -0,0 +1,130 @@
# Created by .ignore support plugin (hsz.mobi)
### Eclipse template
*.pydevproject
.metadata
.gradle
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath

# Eclipse Core
.project

# External tool builders
.externalToolBuilders/

# Locally stored "Eclipse launch configurations"
*.launch

# CDT-specific
.cproject

# JDT-specific (Eclipse Java Development Tools)
.classpath

# Java annotation processor (APT)
.factorypath

# PDT-specific
.buildpath

# sbteclipse plugin
.target

# TeXlipse plugin
.texlipse
### Maven template
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio

*.iml

## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:

# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries

# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml

# Gradle:
# .idea/gradle.xml
# .idea/libraries

# Mongo Explorer plugin:
# .idea/mongoSettings.xml

## File-based project format:
*.ipr
*.iws

## Plugin-specific files:

# IntelliJ
/out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Java template
*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Scala template
*.class
*.log

# sbt specific
.cache
.history
.lib/
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/

# Scala-IDE specific
.scala_dependencies
.worksheet

3 changes: 3 additions & 0 deletions rest-spray-io/backend/src/main/resources/application.conf
@@ -0,0 +1,3 @@
spray.servlet {
boot-class = "io.udash.demos.rest.api.SprayBoot"
}
22 changes: 22 additions & 0 deletions rest-spray-io/backend/src/main/resources/logback.xml
@@ -0,0 +1,22 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/udash-guide-${bySecond}.log</file>
<append>true</append>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
@@ -0,0 +1,12 @@
package io.udash.demos.rest

import io.udash.demos.rest.jetty.ApplicationServer

object Launcher {
def main(args: Array[String]): Unit = {
val server = new ApplicationServer(8080, "backend/target/UdashStatic/WebContent")
server.start()
}
}


@@ -0,0 +1,143 @@
package io.udash.demos.rest.api

import akka.actor.{Actor, ActorRefFactory, Props}
import com.avsystem.commons.serialization.GenCodec
import io.udash.demos.rest.model._
import io.udash.demos.rest.services.{ContactService, InMemoryContactService, InMemoryPhoneBookService, PhoneBookService}
import io.udash.rpc.DefaultUdashSerialization
import spray.http.{HttpEntity, MediaTypes, StatusCodes}
import spray.httpx.marshalling.{Marshaller, ToResponseMarshallable, ToResponseMarshaller}
import spray.httpx.unmarshalling.Unmarshaller
import spray.routing.{HttpServiceBase, RequestContext}

import scala.concurrent.ExecutionContext

trait PhoneBookWebService extends HttpServiceBase with DefaultUdashSerialization {
val phoneBookService: PhoneBookService
val contactService: ContactService

implicit def optionMarshaller[T](implicit codec: GenCodec[T]) =
ToResponseMarshaller.fromMarshaller()(gencodecMarshaller[Option[T]](GenCodec.optionCodec(codec)))

implicit def gencodecMarshaller[T](implicit codec: GenCodec[T]): Marshaller[T] = Marshaller.of[T](MediaTypes.`application/json`)(
(value, contentType, ctx) => {
var string: String = null
val output = outputSerialization((serialized) => string = serialized)
codec.write(output, value)
ctx.marshalTo(HttpEntity(contentType, string))
}
)

implicit def gencodecUnmarshaller[T](implicit codec: GenCodec[T]): Unmarshaller[T] = Unmarshaller[T](MediaTypes.`application/json`) {
case HttpEntity.NonEmpty(contentType, data) =>
val input = inputSerialization(data.asString)
codec.read(input)
}

private def completeIfNonEmpty[T](ctx: RequestContext)(opt: Option[T])(implicit rm: ToResponseMarshaller[T]) =
opt match {
case Some(v) => complete(ToResponseMarshallable.isMarshallable(v))(ctx)
case None => complete(StatusCodes.NotFound)(ctx)
}

val route = {
pathPrefix("book") {
pathPrefix(Segment) { segment =>
val bookId = PhoneBookId(segment.toInt)
pathPrefix("contacts") {
pathPrefix("count") {
get {
/** Adds contact to phone book */
complete { phoneBookService.contactsCount(bookId) }
}
} ~
pathPrefix("manage") {
post {
/** Adds contact to phone book */
entity(as[ContactId]) { contactId =>
complete { phoneBookService.addContact(bookId, contactId) }
}
} ~
delete {
/** Removes contact from phone book */
entity(as[ContactId]) { contactId =>
complete { phoneBookService.removeContact(bookId, contactId) }
}
}
} ~
get {
/** Return contacts ids from selected book */
complete { phoneBookService.contacts(bookId) }
}
} ~
get { ctx =>
/** Return phone book info */
completeIfNonEmpty(ctx) { phoneBookService.load(bookId) }
} ~
put {
/** Updates phone book info */
entity(as[PhoneBookInfo]) { phoneBookInfo =>
complete { phoneBookService.update(bookId, phoneBookInfo) }
}
} ~
delete { ctx =>
/** Removes phone book */
completeIfNonEmpty(ctx) { phoneBookService.remove(bookId) }
}
} ~
get {
/** Return phone books list */
complete { phoneBookService.load() }
} ~
post {
/** Creates new phone book */
entity(as[PhoneBookInfo]) { phoneBookInfo =>
complete { phoneBookService.create(phoneBookInfo) }
}
}
} ~
pathPrefix("contact") {
path(Segment) { segment =>
val contactId = ContactId(segment.toInt)
get { ctx =>
/** Return contact details */
completeIfNonEmpty(ctx) { contactService.load(contactId) }
} ~
put {
/** Updates contact */
entity(as[Contact]) { contact =>
complete { contactService.update(contactId, contact) }
}
} ~
delete { ctx =>
/** Removes contact */
completeIfNonEmpty(ctx) { contactService.remove(contactId) }
phoneBookService.contactRemoved(contactId)
}
} ~
get {
/** Creates new contact */
complete { contactService.load() }
} ~
post {
/** Creates new contact */
entity(as[Contact]) { contact =>
complete { contactService.create(contact) }
}
}
}
}
}

object PhoneBookWebServiceActor {
def props(prefix: String = "") = Props(classOf[PhoneBookWebServiceActor], prefix)
}

class PhoneBookWebServiceActor(prefix: String) extends Actor with PhoneBookWebService {
implicit lazy val executionContext: ExecutionContext = context.dispatcher
def actorRefFactory: ActorRefFactory = context
def receive = runRoute(if (prefix.nonEmpty) pathPrefix(prefix) {route} else route)

override val phoneBookService: PhoneBookService = InMemoryPhoneBookService
override val contactService: ContactService = InMemoryContactService
}
@@ -0,0 +1,10 @@
package io.udash.demos.rest.api

import akka.actor.{ActorRef, ActorSystem}
import io.udash.demos.rest.jetty.ApplicationServer
import spray.servlet.WebBoot

class SprayBoot extends WebBoot {
override def system: ActorSystem = ActorSystem("spray-system")
override def serviceActor: ActorRef = system.actorOf(PhoneBookWebServiceActor.props(ApplicationServer.restPrefix))
}
@@ -0,0 +1,42 @@
package io.udash.demos.rest.jetty

import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.gzip.GzipHandler
import org.eclipse.jetty.server.session.SessionHandler
import org.eclipse.jetty.servlet.{DefaultServlet, ServletContextHandler, ServletHolder}

class ApplicationServer(val port: Int, resourceBase: String) {
private val server = new Server(port)
private val contextHandler = new ServletContextHandler

contextHandler.setSessionHandler(new SessionHandler)
contextHandler.setGzipHandler(new GzipHandler)
server.setHandler(contextHandler)

def start() = server.start()

def stop() = server.stop()

private val appHolder = {
val appHolder = new ServletHolder(new DefaultServlet)
appHolder.setAsyncSupported(true)
appHolder.setInitParameter("resourceBase", resourceBase)
appHolder
}
contextHandler.addServlet(appHolder, "/*")

private val restApiHolder = {
import spray.servlet.Servlet30ConnectorServlet
import spray.servlet.Initializer

contextHandler.addEventListener(new Initializer())
val apiHolder = new ServletHolder(new Servlet30ConnectorServlet)
apiHolder
}
contextHandler.addServlet(restApiHolder, s"/${ApplicationServer.restPrefix}/*")
}

object ApplicationServer {
val restPrefix = "api"
}

0 comments on commit d745c2a

Please sign in to comment.