package com.jetdrone.vertx.yoke.json;
import io.vertx.core.json.JsonObject;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public final class JsonSchemaResolver {
public static final class Schema extends HashMap<String, Object> {
private static final long serialVersionUID = 1l;
private Schema parent;
private final String id;
private Schema(Map<String, Object> map) {
this(map, (String) map.get("id"));
}
private Schema(Map<String, Object> map, String id) {
super(map);
this.id = id;
}
public void setParent(Schema parent) {
this.parent = parent;
}
public Schema getParent() {
return parent;
}
public String getId() {
return id;
}
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) super.get(key);
}
}
private static final Pattern ABSOLUTE = Pattern.compile("^.*://.*");
private static Map<String, Schema> loadedSchemas = new HashMap<>();
public static Schema resolveSchema(String uri) {
return resolveSchema(uri, null);
}
public synchronized static Schema resolveSchema(String uri, Schema parent) {
uri = resolveUri(uri, parent);
if (!loadedSchemas.containsKey(uri)) {
tryToLoad(uri);
}
return loadedSchemas.get(uri);
}
public static Schema resolveSchema(Map<String, Object> schema) {
return new JsonSchemaResolver.Schema(schema);
}
public static Schema resolveSchema(Map<String, Object> schema, Schema parent) {
final Schema _schema = new JsonSchemaResolver.Schema(schema);
_schema.setParent(parent);
return _schema;
}
private static String resolveUri(String uri, Schema parent) {
// if it is an absolute URI return it, nothing to resolve
if (ABSOLUTE.matcher(uri).matches()) {
return uri;
}
if (parent == null) {
throw new RuntimeException("relative URI without a base URI");
}
String parentBaseUri;
int idx = parent.getId().indexOf('#');
if (idx != -1) {
parentBaseUri = parent.getId().substring(0, parent.getId().indexOf('#'));
} else {
parentBaseUri = parent.getId();
}
if (uri.charAt(0) == '#') {
return parentBaseUri + uri;
}
throw new RuntimeException("non relative URI");
}
private static void tryToLoad(String ref) {
try {
JsonObject json;
final URI uri = new URI(ref);
final String scheme = uri.getScheme();
// there is a scheme so we can load from http, classpath, or file
switch (scheme) {
case "classpath":
json = loadFromClasspath(uri);
break;
case "http":
case "https":
json = loadFromURL(uri);
break;
case "file":
json = loadFromFile(uri);
break;
default:
throw new RuntimeException("Unknown Protocol: " + scheme);
}
final Schema schema = new Schema(json.getMap(), ref);
final String schemaId = schema.getId();
if (schemaId != null) {
if (loadedSchemas.containsKey(schemaId)) {
throw new RuntimeException("Schema ID [" + schemaId + "] already in use!");
}
// register the schema into the registry using its Id
loadedSchemas.put(schemaId, schema);
} else {
if (loadedSchemas.containsKey(ref)) {
throw new RuntimeException("Schema URI [" + uri + "] already in use!");
}
// register the schema into the registry using its URI
loadedSchemas.put(ref, schema);
}
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
private static JsonObject loadFromURL(final URI uri) {
try {
try (Reader r = new BufferedReader(new InputStreamReader(uri.toURL().openStream(), "UTF-8"))) {
Writer writer = new StringWriter();
char[] buffer = new char[1024];
int n;
while ((n = r.read(buffer)) != -1) {
writer.write(buffer, 0, n);
}
final JsonObject json = new JsonObject(writer.toString());
final String fragment = uri.getFragment();
if (fragment != null) {
if (json.containsKey(fragment)) {
return json.getJsonObject(fragment);
} else {
throw new RuntimeException("Fragment #" + fragment + " not found!");
}
}
return json;
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
private static JsonObject loadFromClasspath(final URI uri) {
try {
final String path = uri.getPath();
if (path == null || "".equals(path)) {
throw new RuntimeException("Invalid path [" + uri.toString() + "]");
}
try (Reader r = new BufferedReader(new InputStreamReader(JsonSchemaResolver.class.getResourceAsStream(path), "UTF-8"))) {
Writer writer = new StringWriter();
char[] buffer = new char[1024];
int n;
while ((n = r.read(buffer)) != -1) {
writer.write(buffer, 0, n);
}
final JsonObject json = new JsonObject(writer.toString());
final String fragment = uri.getFragment();
if (fragment != null && !"".equals(fragment)) {
String[] nodes = fragment.split("/");
JsonObject subjson = json;
for (int i = "".equals(nodes[0]) ? 1 : 0 ; i < nodes.length; i++) {
if (subjson.containsKey(nodes[i])) {
subjson = subjson.getJsonObject(nodes[i]);
} else {
throw new RuntimeException("Fragment Node #" + nodes[i] + " not found!");
}
}
return subjson;
}
return json;
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
private static JsonObject loadFromFile(final URI uri) {
try {
final String path = uri.getPath();
if (path == null || "".equals(path)) {
throw new RuntimeException("Invalid path [" + uri.toString() + "]");
}
try (Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"))) {
Writer writer = new StringWriter();
char[] buffer = new char[1024];
int n;
while ((n = r.read(buffer)) != -1) {
writer.write(buffer, 0, n);
}
final JsonObject json = new JsonObject(writer.toString());
final String fragment = uri.getFragment();
if (fragment != null) {
if (json.containsKey(fragment)) {
return json.getJsonObject(fragment);
} else {
throw new RuntimeException("Fragment #" + fragment + " not found!");
}
}
return json;
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}