From c7b619402f290fdf77312af5f426eb6bd0d5e5f7 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 19 May 2013 23:09:39 +0200 Subject: o Implementing my own model of collection+json. My browser want to be more relaxed than what normally makes sense. o Implementing the server side of the queries. --- project/Build.scala | 2 +- src/main/scala/io/trygvis/cj/Explorer.scala | 31 +++- src/main/scala/io/trygvis/cj/Json4sHelpers.scala | 47 ++++++ src/main/scala/io/trygvis/cj/Views.scala | 192 +++++++++-------------- src/main/scala/io/trygvis/cj/model.scala | 79 ++++++++++ 5 files changed, 229 insertions(+), 122 deletions(-) create mode 100644 src/main/scala/io/trygvis/cj/Json4sHelpers.scala create mode 100644 src/main/scala/io/trygvis/cj/model.scala diff --git a/project/Build.scala b/project/Build.scala index 1542f8f..8f11f07 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -23,7 +23,7 @@ object Build extends sbt.Build { settings = buildSettings ++ Seq( description := "Collection+JSON Explorer", name := "collection-json-explorer", - libraryDependencies += "net.hamnaberg.rest" %% "scala-json-collection" % "2.1-SNAPSHOT", + libraryDependencies += "org.json4s" %% "json4s-native" % "3.1.0", libraryDependencies += "net.databinder" %% "unfiltered-filter" % "0.6.8", libraryDependencies += "net.databinder" %% "unfiltered-directives" % "0.6.8", libraryDependencies += "net.databinder" %% "unfiltered-jetty" % "0.6.8", diff --git a/src/main/scala/io/trygvis/cj/Explorer.scala b/src/main/scala/io/trygvis/cj/Explorer.scala index 9f8fd44..9c4206f 100644 --- a/src/main/scala/io/trygvis/cj/Explorer.scala +++ b/src/main/scala/io/trygvis/cj/Explorer.scala @@ -2,10 +2,9 @@ package io.trygvis.cj import scala.collection.JavaConversions._ import scala.io.Source -import java.net.{HttpURLConnection, URI} -import java.io.{Writer, StringWriter, PrintWriter, InputStreamReader} +import java.net.{URLEncoder, HttpURLConnection, URI} +import java.io._ import javax.servlet.http.HttpServletRequest -import net.hamnaberg.json.collection.{NativeJsonCollectionParser, JsonCollection} import unfiltered.request._ import unfiltered.response._ import unfiltered.filter._ @@ -53,13 +52,33 @@ class Browser extends Plan { views = viewsX(r) } yield { println("url=" + url) - val uri = URI.create(url) + + val targetParams = params flatMap { + case (key, value) if key.startsWith("param-") => + // It makes no sense to have duplicate query keys so just pick the first one. This is most likely the + // same as most web framework does when given multiple query parameters. + Some(key.substring(6), value.headOption getOrElse "") + case _ => + None + } + + println("targetParam=" + targetParams) + + val q = targetParams map { case (key, value) => + URLEncoder.encode(key, "utf-8") + "=" + URLEncoder.encode(value, "utf-8") + } + + println("q=" + q) + + val uri = URI.create(url + (if(q.nonEmpty) "?" + q.reduce (_ ++ "&" ++ _) else "")) + println("uri=" + uri) + val con = uri.toURL.openConnection().asInstanceOf[HttpURLConnection] con.setRequestProperty("accept", "application/vnd.collection+json") val content = Source.fromInputStream(con.getInputStream, "utf-8").mkString("") val headers = con.getHeaderFields.toMap filter {case (key, _) => key != null} - val result = NativeJsonCollectionParser.parseCollection(content) - Ok ~> Html5(views.data(uri, params, result, CjResponse(con.getResponseCode, con.getResponseMessage, headers))) + val result = Collection.parseCollection(new StringReader(content)) + Ok ~> Html5(views.data(uri, targetParams, result, CjResponse(con.getResponseCode, con.getResponseMessage, headers))) } } } diff --git a/src/main/scala/io/trygvis/cj/Json4sHelpers.scala b/src/main/scala/io/trygvis/cj/Json4sHelpers.scala new file mode 100644 index 0000000..0241e59 --- /dev/null +++ b/src/main/scala/io/trygvis/cj/Json4sHelpers.scala @@ -0,0 +1,47 @@ +package io.trygvis.cj + +import org.json4s._ + +object Json4sHelpers { + def getAsObjectList(obj: JObject, name: String): List[JObject] = { + (obj \ name) match { + case JArray(values) => values.collect{case o@JObject(_) => o} + case o@JObject(_) => List(o) + case _ => Nil + } + } + + def getAsValueList(obj: JObject, name: String): List[JValue] = { + (obj \ name) match { + case JNothing => Nil + case JArray(values) => values + case j => List(j) + } + } + + def getAsObject(obj: JObject, name: String): Option[JObject] = { + (obj \ name) match { + case o@JObject(_) => Some(o) + case _ => None + } + } + + def getAsString(obj: JObject, name: String): Option[String] = { + (obj \ name) match { + case JString(s) => Some(s) + case _ => None + } + } + + def replace(obj: JObject, name: String, value: JValue): JObject = { + val map = obj.obj.toMap - name + JObject(map.updated(name, value).toList) + } + + def filtered(obj: JObject): JObject = { + JObject(obj.obj.filter { + case JField(_, JNothing) => false + case _ => true + }) + } +} diff --git a/src/main/scala/io/trygvis/cj/Views.scala b/src/main/scala/io/trygvis/cj/Views.scala index 869677e..b4429d8 100644 --- a/src/main/scala/io/trygvis/cj/Views.scala +++ b/src/main/scala/io/trygvis/cj/Views.scala @@ -3,8 +3,6 @@ package io.trygvis.cj import scala.collection.JavaConversions._ import scala.xml.{Group, NodeSeq, Elem} import java.net.{MalformedURLException, URL, URI, URLEncoder} -import net.hamnaberg.json.collection.{Property, Link, Json4sHelpers, JsonCollection} -import net.hamnaberg.json.collection.Render.IMAGE import org.json4s.native.JsonMethods import java.io.{PrintWriter, StringWriter, Writer} @@ -41,18 +39,8 @@ class Views(baseUrl: String) { result.toString } - /* - mixin get_name(link, prefix, i) - - var name = typeof link.name == 'string' ? link.name : undefined - - var prompt = typeof link.prompt == 'string' ? link.prompt : undefined - - var prefix = typeof prefix == 'string' ? prefix + ': ' : '' - |#{prefix + (name || prompt || '#' + i)} - - */ def getName(link: Link, prefix: String, i: Int) = { -// var name = link.name - val name = Json4sHelpers.getAsString(link.underlying, "name") - prefix + name.orElse(link.prompt).getOrElse("#" + i) + prefix + link.name.orElse(link.prompt).getOrElse("#" + i) } def index = { @@ -132,17 +120,16 @@ class Views(baseUrl: String) { layout(content, None) } - def data(url: URI, params: Map[String, Seq[String]], result: Either[Throwable, JsonCollection], res: CjResponse) = { + def data(url: URI, targetParams: Map[String, String], result: Either[Throwable, Collection], res: CjResponse) = { def href(uri: URI) = { // val splits = uri.getPath.split('/') // for split in splits -// a(href=urlgenerator.render(split[1]), title='Explore #{split[1]}') #{split[0]} - uri.toURL.toExternalForm +// a(href=url generator.render(split[1]), title='Explore #{split[1]}') #{split[0]} + {uri.toURL.toExternalForm} } def link(link: Link) = { - val name = Json4sHelpers.getAsString(link.underlying, "name")
Explore @@ -154,53 +141,51 @@ class Views(baseUrl: String) {
rel
{tryLink(link.rel)}
name
-
{name.getOrElse(notSet)}
+
{link.name.getOrElse(notSet)}
prompt
{link.prompt.getOrElse(notSet)}
render
-
{link.render.map(_.name).getOrElse(notSet)}
- {link.render match { - case Some(IMAGE) => -
Image
-
- - {name.getOrElse("")} - -
- case _ => - NodeSeq.Empty - }} +
{link.render.getOrElse(notSet)}
+ {if(link.render == "image") +
Image
+
+ + {link.name.getOrElse("")} + +
+ } } - def meta(implicit cj: JsonCollection) = + def meta(implicit cj: Collection) =
version
-
{cj.version.name}
+
{cj.version}
href
-
-
{href(cj.href)}
-
+
{cj.href.map(href).getOrElse(notSet)}
-
-
-

- Explore - Raw - Delete -

- - - -
-

+ {if(cj.href.isDefined) { + val url = cj.href.get +
+
+

+ Explore + Raw + Delete +

+ + + +
+

+
-
+ }} {cj.links match { case Nil => NodeSeq.Empty @@ -208,22 +193,18 @@ class Views(baseUrl: String) {

Collection Links

{cj.links.zipWithIndex.map { case (l, i) => - val name = Json4sHelpers.getAsString(l.underlying, "name") - val title = l.prompt.orElse(name) match { - case Some(t) => ": " + t - case _ => "" - } - Group(Seq(, link(l))) + val title = l.prompt.orElse(l.name).map(": " + _).getOrElse("") + Group(Seq(, link(l))) }}
}} - // TODO: If the collection has prev/next links, add buttons to automaticaly navigate those. + // TODO: If the collection has prev/next links, add buttons to automatically navigate those. // TODO: Add ability to show the raw part of the collection. - def items(cj: JsonCollection) = { + def items(cj: Collection) = { - def itemLinks(cj: JsonCollection) = { + def itemLinks(cj: Collection) = { val first = cj.findLinkByRel("first") val prev = cj.findLinkByRel("prev") val next = cj.findLinkByRel("next") @@ -247,21 +228,24 @@ class Views(baseUrl: String) { val links = item.links

Item #{i + 1}

+ {if(item.href.isDefined) { + val url = item.href.get + }}
href
-
{href(item.href)}
+
{item.href.map(href).getOrElse(notSet)}
@@ -277,25 +261,25 @@ class Views(baseUrl: String) {
- - {item.data map { d => }} + {item.data map { d => }}
{d.name}{d.value}
{d.name}{d.value getOrElse ""}
+ {if(item.href.isDefined) { + val uri = item.href.get +