package com.mockey.ui; import java.io.IOException; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.fge.jsonschema.JsonValidators; import com.github.fge.jsonschema.constants.ParseError; import com.github.fge.jsonschema.constants.ValidateResponse; import com.github.fge.jsonschema.main.JsonValidator; import com.github.fge.jsonschema.report.ProcessingReport; import com.github.fge.jsonschema.util.AsJson; import com.github.fge.jsonschema.util.JsonLoader; import com.google.common.annotations.VisibleForTesting; import com.mockey.ServiceException; public class JsonSchemaUtil { public static boolean validData(final String rawSchema, final String rawData) { boolean valid = false; try { final ProcessingReport report = buildReport(rawSchema, rawData, true, false); valid = report.isSuccess(); } catch (ServiceException e) { return valid; } catch (IOException e) { return valid; } return valid; } /* * Build the response. When we arrive here, we are guaranteed that we have * the needed elements. */ @VisibleForTesting public static JsonNode buildResult(final String rawSchema, final String rawData, final boolean useV3, final boolean useId) throws IOException { final ObjectNode ret = JsonNodeFactory.instance.objectNode(); try { final ProcessingReport report = buildReport(rawSchema, rawData, useV3, useId); final boolean success = report.isSuccess(); ret.put(ValidateResponse.VALID, success); ret.put(ValidateResponse.RESULTS, ((AsJson) report).asJson()); return ret; } catch (ServiceException e) { return ret; } } private static ProcessingReport buildReport(final String rawSchema, final String rawData, final boolean useV3, final boolean useId) throws IOException, ServiceException { final ObjectNode ret = JsonNodeFactory.instance.objectNode(); final boolean invalidSchema = fillWithData(ret, ValidateResponse.SCHEMA, ValidateResponse.INVALID_SCHEMA, rawSchema); final boolean invalidData = fillWithData(ret, ValidateResponse.DATA, ValidateResponse.INVALID_DATA, rawData); if (invalidSchema || invalidData) { throw new ServiceException("Schema valid? " + invalidSchema + " Data valid?" + invalidData); } final JsonNode schemaNode = ret.remove(ValidateResponse.SCHEMA); final JsonNode data = ret.remove(ValidateResponse.DATA); final JsonValidator validator = JsonValidators.withOptions(useV3, useId); final ProcessingReport report = validator.validateUnchecked(schemaNode, data); return report; } /* * We have to use that since Java is not smart enough to detect that * sometimes, a variable is initialized in all paths. * * This returns true if the data is invalid. */ private static boolean fillWithData(final ObjectNode node, final String onSuccess, final String onFailure, final String raw) throws IOException { try { node.put(onSuccess, JsonLoader.fromString(raw)); return false; } catch (JsonProcessingException e) { node.put(onFailure, buildParsingError(e, raw.contains("\r\n"))); return true; } } private static JsonNode buildParsingError(final JsonProcessingException e, final boolean crlf) { final JsonLocation location = e.getLocation(); final ObjectNode ret = JsonNodeFactory.instance.objectNode(); /* * Unfortunately, for some reason, Jackson botches the column number in * its JsonPosition -- I cannot figure out why exactly. However, it does * have a correct offset into the buffer. * * The problem is that if the input has CR/LF line terminators, its * offset will be "off" by the number of lines minus 1 with regards to * what JavaScript sees as positions in text areas. Make the necessary * adjustments so that the caret jumps at the correct position in this * case. */ final int lineNr = location.getLineNr(); int offset = (int) location.getCharOffset(); if (crlf) offset = offset - lineNr + 1; ret.put(ParseError.LINE, lineNr); ret.put(ParseError.OFFSET, offset); // Finally, put the message ret.put(ParseError.MESSAGE, e.getOriginalMessage()); return ret; } }