Skip to content
This repository has been archived by the owner on Dec 21, 2019. It is now read-only.

Commit

Permalink
Merge pull request #105 from edgarmueller/scala-2.11
Browse files Browse the repository at this point in the history
Fix for issue #103: schema passed as URL argument is not cached
  • Loading branch information
edgarmueller committed Feb 21, 2017
2 parents a301e53 + 5875fd2 commit d932547
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 87 deletions.
155 changes: 80 additions & 75 deletions src/main/scala/com/eclipsesource/schema/SchemaValidator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,70 @@ trait HasLang {
implicit val lang: Lang
}

trait CanValidate {
self: Customizations with HasLang =>
/**
* The schema validator.
*
* @param refResolver the reference resolver
*/
case class SchemaValidator(refResolver: SchemaRefResolver = new SchemaRefResolver,
formats: Map[String, SchemaFormat] = DefaultFormats.formats)
(implicit val lang: Lang = Lang.Default)
extends Customizations with HasLang {

/**
* Add a URLStreamHandler that is capable of handling absolute with a specific scheme.
*
* @param handler the UrlHandler to be added
* @return a new validator instance
*/
def addUrlHandler(handler: URLStreamHandler, scheme: String): SchemaValidator =
copy(refResolver =
refResolver.copy(resolverFactory =
refResolver.resolverFactory.addUrlHandler(scheme, handler)))

/**
* Add a relative URLStreamHandler that is capable of resolving relative references.
* Optionally takes a protocol that determines for which schemes the
* handler should be triggered.
*
* @param handler the relative handler to be added
* @return the validator instance with the handler being added
*/
def addRelativeUrlHandler(handler: URLStreamHandler, scheme: String = UrlHandler.ProtocolLessScheme): SchemaValidator =
copy(refResolver =
refResolver.copy(resolverFactory =
refResolver.resolverFactory.addRelativeUrlHandler(scheme, handler)))


/**
* Add a custom format
*
* @param format the custom format
* @return a new validator instance containing the custom format
*/
def addFormat(format: SchemaFormat): SchemaValidator =
copy(formats = formats + (format.name -> format))

/**
* Add a schema.
*
* @param id the id of the schema
* @param schema the schema
*/
def addSchema(id: String, schema: SchemaType): SchemaValidator = {
copy(refResolver =
refResolver.copy(cache =
refResolver.cache.add(Ref(id))(schema))
)
}

private def buildContext(schema: SchemaType, schemaUrl: URL): SchemaResolutionContext = {
val id = schema.constraints.any.id.map(Ref)
SchemaResolutionContext(refResolver,
new SchemaResolutionScope(schema, id.orElse(Some(Ref(schemaUrl.toString)))),
formats = formats
)
}

/**
* Validate the given JsValue against the schema located at the given URL.
Expand All @@ -33,18 +95,21 @@ trait CanValidate {
* @return a JsResult holding the validation result
*/
def validate(schemaUrl: URL, input: => JsValue): JsResult[JsValue] = {
def buildContext(schema: SchemaType): SchemaResolutionContext = {
val id = schema.constraints.any.id.map(Ref)
SchemaResolutionContext(refResolver,
new SchemaResolutionScope(schema, id.orElse(Some(Ref(schemaUrl.toString)))),
formats = formats
)
val ref = Ref(schemaUrl.toString)
refResolver.cache.get(ref) match {
case None =>
for {
schema <- JsonSource.schemaFromUrl(schemaUrl)
context = buildContext(schema, schemaUrl)
result <- schema.validate(input, context).toJsResult
} yield {
refResolver.cache = refResolver.cache.add(ref)(schema)
result
}
case Some(schema) =>
val context = buildContext(schema, schemaUrl)
schema.validate(input, context).toJsResult
}

for {
schema <- JsonSource.schemaFromUrl(schemaUrl)
result <- schema.validate(input, buildContext(schema)).toJsResult
} yield result
}

/**
Expand Down Expand Up @@ -111,10 +176,8 @@ trait CanValidate {
new SchemaResolutionScope(schema, id),
formats = formats
)
schema.validate(
input,
context
).toJsResult

schema.validate(input, context).toJsResult
}

/**
Expand Down Expand Up @@ -199,61 +262,3 @@ trait CanValidate {
}
}
}

/**
* The schema validator.
*
* @param refResolver the reference resolver
*/
case class SchemaValidator(refResolver: SchemaRefResolver = new SchemaRefResolver,
formats: Map[String, SchemaFormat] = DefaultFormats.formats)
(implicit val lang: Lang = Lang.Default)
extends CanValidate with Customizations with HasLang {

/**
* Add a URLStreamHandler that is capable of handling absolute with a specific scheme.
*
* @param handler the UrlHandler to be added
* @return a new validator instance
*/
def addUrlHandler(handler: URLStreamHandler, scheme: String): SchemaValidator =
copy(refResolver =
refResolver.copy(resolverFactory =
refResolver.resolverFactory.addUrlHandler(scheme, handler)))

/**
* Add a relative URLStreamHandler that is capable of resolving relative references.
* Optionally takes a protocol that determines for which schemes the
* handler should be triggered.
*
* @param handler the relative handler to be added
* @return the validator instance with the handler being added
*/
def addRelativeUrlHandler(handler: URLStreamHandler, scheme: String = UrlHandler.ProtocolLessScheme): SchemaValidator =
copy(refResolver =
refResolver.copy(resolverFactory =
refResolver.resolverFactory.addRelativeUrlHandler(scheme, handler)))


/**
* Add a custom format
*
* @param format the custom format
* @return a new validator instance containing the custom format
*/
def addFormat(format: SchemaFormat): SchemaValidator =
copy(formats = formats + (format.name -> format))

/**
* Add a schema.
*
* @param id the id of the schema
* @param schema the schema
*/
def addSchema(id: String, schema: SchemaType): SchemaValidator = {
copy(refResolver =
refResolver.copy(cache =
refResolver.cache.add(Ref(id))(schema))
)
}
}
31 changes: 19 additions & 12 deletions src/test/scala/com/eclipsesource/schema/SchemaValidatorSpec.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.eclipsesource.schema

import java.net.{URL, URLConnection, URLStreamHandler}
import java.net.URL

import com.eclipsesource.schema.urlhandlers.ClasspathUrlHandler
import controllers.Assets
import play.api.Application
import play.api.inject.guice.GuiceApplicationBuilder
import play.api.libs.functional.syntax._
import play.api.libs.json._
Expand All @@ -16,9 +16,9 @@ class SchemaValidatorSpec extends PlaySpecification {
case (_, path) => Assets.versioned("/", path)
}

def createApp = new GuiceApplicationBuilder().routes(routes).build()
def createApp: Application = new GuiceApplicationBuilder().routes(routes).build()

val schema = JsonSource.schemaFromString(
val schema: SchemaType = JsonSource.schemaFromString(
"""{
| "type": "object",
| "properties": {
Expand All @@ -38,14 +38,14 @@ class SchemaValidatorSpec extends PlaySpecification {
case class Location(name: String)
case class Talk(location: Location)

implicit val locationReads = Json.reads[Location]
val talkReads = Json.reads[Talk]
implicit val locationWrites = Json.writes[Location]
val talkWrites = Json.writes[Talk]
implicit val talkFormat = Json.format[Talk]
implicit val locationReads: Reads[Location] = Json.reads[Location]
val talkReads: Reads[Talk] = Json.reads[Talk]
implicit val locationWrites: OWrites[Location] = Json.writes[Location]
val talkWrites: OWrites[Talk] = Json.writes[Talk]
implicit val talkFormat: OFormat[Talk] = Json.format[Talk]

val resourceUrl: URL = getClass.getResource("/talk.json")
val instance = Json.obj(
val instance: JsObject = Json.obj(
"location" -> Json.obj(
"name" -> "Munich"
)
Expand Down Expand Up @@ -78,7 +78,7 @@ class SchemaValidatorSpec extends PlaySpecification {
|}""".stripMargin).get
val result = SchemaValidator().validate(schema)(Json.arr(1,2,3))
result.asOpt must beSome.which {
case arr@JsArray(seq) => seq must haveLength(3)
case JsArray(seq) => seq must haveLength(3)
}
}

Expand Down Expand Up @@ -320,6 +320,13 @@ class SchemaValidatorSpec extends PlaySpecification {
val talk = Talk(Location("Munich"))
val validator = SchemaValidator()
validator.validate(resourceUrl, talk)
validator.refResolver.cache.mapping.isEmpty must beFalse
validator.refResolver.cache.mapping.contains("http://json-schema.org/geo#") must beTrue
}

"verify cache hit for given URL" in {
val talk = Talk(Location("Munich"))
val validator = SchemaValidator()
validator.validate(resourceUrl, talk)
validator.refResolver.cache.mapping.contains(resourceUrl.toString) must beTrue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.eclipsesource.schema

import org.specs2.mutable.Specification
import play.api.libs.json.Json

class SimplePerformanceSpec extends Specification {

def timed(name: String)(body: => Unit) {
val start = System.currentTimeMillis()
body
println(name + ": " + (System.currentTimeMillis() - start) + " ms")
}

val validator = SchemaValidator()
val schemaUrl = getClass.getResource("/issue-99-1.json")
val schema = JsonSource.schemaFromUrl(schemaUrl).get

val instance = Json.parse(
"""{
|"mything": "the thing"
|}""".stripMargin)

timed("preloaded") {
for (_ <- 1 to 1000) validator.validate(schema, instance)
}
timed("url based") {
for (_ <- 1 to 1000) validator.validate(schemaUrl, instance)
}

}

0 comments on commit d932547

Please sign in to comment.