Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
d1egoaz committed Aug 5, 2016
0 parents commit 4aa3b35
Show file tree
Hide file tree
Showing 13 changed files with 928 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# IDE / editor files
*.iml
.idea/
.idea_modules
*.swp
.vagrant

# Ignore OSX stuff
.DS_Store
*/tags
tags

# Ignore generated stuff
logs/
**/target/
.classpath
.project
.settings
RUNNING_PID
akka-diagnostics/
output/
*.orig

# Deployment templates
deploy/*/*.json
conf/logback.xml

674 changes: 674 additions & 0 deletions COPYING

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
This software is licensed under the Apache 2 license, quoted below.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with
the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
86 changes: 86 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
* PlantUml Rest Service API


I created this tool trying to improve my local workflow as when I needed to
render a new image I had to run =java -jar plantuml.jar= and JVM warm up is slow :(

Also I use org-mode to render PlantUml directly on emacs, but calling an
external java process it just destroys the workflow.

I tried to use https://github.com/plantuml/plantuml-server but server is based on JEE and
the protocol to call server needs some custom encoding. See [[http://plantuml.com/jquery_plantuml.js][js]] and [[https://github.com/dougn/python-plantuml/blob/master/plantuml.py][python]] remote clients.

You can also use the server provided by PlantUml in http://www.plantuml.com/plantuml/png/, or
might want to use all the available services online, but most of them are UI based and
sometimes you don't want to paste your diagrams on public services.

So, I just wrote a service that exposes an endpoint to create an image based on a
plantuml string.
I keep the service running on the background so there is no more JVM warming up involved.

This service can be integrated with any decent tool/IDE as it's relaying on HTTP.

** Tools used

I think it's a good example of newcomers to Scala/Play as it's using routing, DI, custom dispatchers.
And it has Unit Tests :p

*** [[https://www.playframework.com/][Play Framework 2.5.4]]

*** [[http://plantuml.com/][PlantUml]]


** Example

#+NAME: plantumltext
#+BEGIN_EXAMPLE
@startuml
Diego -> World: hello
@enduml
#+END_EXAMPLE


#+BEGIN_SRC sh :file /tmp/plantuml.png :var text=plantumltext :exports both
echo "${text}" > /tmp/plantumltext.txt
curl -s -X POST --data-binary @/tmp/plantumltext.txt -H "Content-Type:text/plain" localhost:9000/plantuml/png > /tmp/plantuml.png
#+END_SRC

#+RESULTS:
[[file:/tmp/plantuml.png]]

If you're using =curl= remember to use `--data-binary` instead of `-d` to preserve line breaks

To know more about PlantUML, please visit http://plantuml.com/

** Creating a fat jar

See https://www.playframework.com/documentation/2.5.x/Deploying#Using-the-dist-task

** Using a precompiled jar

Just go to [[https://github.com/d1egoaz/plantuml-rest-service/releases][Releases]].
Download the latest version.

To run the application, unzip the file on the target server, and then run the script in the bin directory.
Executable script comes in two versions, a bash shell script, and a windows .bat script.

#+BEGIN_SRC sh
$ unzip plantuml-rest-service-1.0-SNAPSHOT.zip
$ plantuml-rest-service-1.0-SNAPSHOT/bin/plantuml-rest-service
#+END_SRC
#+BEGIN_EXAMPLE
[info] play.api.Play - Application started (Prod)
[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
#+END_EXAMPLE

** License

This project uses PlantUML and that PlantUML is distributed under LGPL.

** Roadmap/TODO:

- Support additional image types.
- json ???
- Rewrite plantuml dependency in go.

Follow me on https://twitter.com/d1egoaz
8 changes: 8 additions & 0 deletions app/Module.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import com.google.inject.AbstractModule
import services.{PlantUml, PlantUmlCreator}

class Module extends AbstractModule {
override def configure(): Unit = {
bind(classOf[PlantUml]).to(classOf[PlantUmlCreator])
}
}
18 changes: 18 additions & 0 deletions app/controllers/PlantUmlController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package controllers

import play.api._
import play.api.mvc._
import scala.concurrent.ExecutionContext
import services.PlantUml

import javax.inject._

@Singleton
class PlantUmlController @Inject() (plantuml: PlantUml)(implicit exec: ExecutionContext) extends Controller {

def createImage: Action[String] = Action.async(parse.text) { request =>
val text = request.body
Logger.debug(s"Converting to PNG\n$text")
plantuml.createImage(text).map(bytes => Ok(bytes).as("image/png"))
}
}
40 changes: 40 additions & 0 deletions app/services/PlantUML.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package services

import akka.actor.ActorSystem
import net.sourceforge.plantuml.SourceStringReader

import scala.concurrent.{ExecutionContext, blocking}
import scala.concurrent.Future
import java.io.ByteArrayOutputStream
import java.io.IOException
import javax.inject._

trait PlantUml {
def createImage(text: String): Future[Array[Byte]]
}

@Singleton
class PlantUmlCreator @Inject() (actorSystem: ActorSystem) extends PlantUml {

private val myExecutionContext: ExecutionContext = actorSystem.dispatchers.lookup("contexts.plantuml")
def createImage(text: String): Future[Array[Byte]] = {
Future {
blocking {
val reader = new SourceStringReader(text, "UTF8")
val baos = new ByteArrayOutputStream()
val image = reader.generateImage(baos)
try {
baos.toByteArray()
} finally {
try {
if (baos != null) { // scalastyle:ignore
baos.close();
}
} catch {
case e: IOException => // ¯\_(ツ)_/¯
}
}
}
}(myExecutionContext)
}
}
12 changes: 12 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name := """plantuml-rest-service"""

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
"net.sourceforge.plantuml" % "plantuml" % "8046",
"org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
)
11 changes: 11 additions & 0 deletions conf/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
play.crypto.secret="@eMlaF6Pl]APrdDCUlUZ;gEJawkKOGGidw=nT5Eq_;nd]d@y5G4p7TLGSUEds1i["

contexts {
plantuml {
executor = "thread-pool-executor"
throughput = 1
thread-pool-executor {
fixed-pool-size = 10
}
}
}
5 changes: 5 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

POST /plantuml/png controllers.PlantUmlController.createImage
4 changes: 4 additions & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#Activator-generated Properties
#Thu Aug 04 17:50:38 PDT 2016
template.uuid=f57599cf-a4f5-4333-9ff1-55d48fd49891
sbt.version=0.13.11
4 changes: 4 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// The Typesafe repository
resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/maven-releases/"

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.4")
31 changes: 31 additions & 0 deletions test/ApplicationSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import org.scalatestplus.play._
import play.api.test._
import play.api.test.Helpers._

class ApplicationSpec extends PlaySpec with OneAppPerTest {

"Routes" should {

"send 404 on a bad request" in {
route(app, FakeRequest(GET, "/boum")).map(status(_)) mustBe Some(NOT_FOUND)
}

}

"PlantUmlController" should {

"creates a PNG image" in {
var text = """
@startuml
Bob -> Alice : hello
@enduml
"""
val home = route(app, FakeRequest(POST, "/plantuml/png").withTextBody(text)).get

status(home) mustBe OK
contentType(home) mustBe Some("image/png")
}

}

}

0 comments on commit 4aa3b35

Please sign in to comment.