/*
* "Copyright (c) 2010-11 The Regents of the University of California.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose, without fee, and without written agreement is
* hereby granted, provided that the above copyright notice, the following
* two paragraphs and the author appear in all copies of this software.
*
* IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
* OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
* CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
*
* Author: Jorge Ortiz (jortiz@cs.berkeley.edu)
* IS4 release version 1.0
*/
package local.json.validator;
import net.sf.json.*;
import net.sf.json.processors.*;
import net.sf.json.util.*;
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.logging.Logger;
import java.util.logging.Level;
public class JSONSchemaValidator{
//Based on the schema defined at the ref below
// ref: http://json-schema.org/
protected JSONObject masterSchema = null;
public static final String TYPE = "type";
public static final String ITEMS = "items";
public static final String DESC = "description";
public static final String ENUM = "enum";
public static final String PROPS = "properties";
public static final String ADDPROPS = "additionalProperties";
public static final String DEFAULT = "default";
public static final String OPTIONAL = "optional";
public static final String SPEC = "specificity";
public static final String UNIQUE = "unique";
public static final String MIN = "minimum";
public static final String MAX = "maximum";
Vector<String> errorVec = new Vector<String>();
public static transient Logger logger = Logger.getLogger(JSONSchemaValidator.class.getPackage().getName());
public JSONSchemaValidator(){
try {
URL masterSchemaURL = new URL("http://www.eecs.berkeley.edu/~jortiz/schemas/schema.json");
BufferedReader in = new BufferedReader(new InputStreamReader(masterSchemaURL.openStream()));
String inputLine=null;
StringBuffer schemaBufDoc = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
schemaBufDoc = schemaBufDoc.append(inputLine).append("\n");
}
//System.out.println(schemaBufDoc);
if(JsonVerifier.isValidJsonValue(schemaBufDoc.toString())) {
masterSchema = (JSONObject) JSONSerializer.toJSON(schemaBufDoc.toString());
}
else {
System.out.println ("Could not parse schema schema");
}
in.close();
} catch (Exception e){
if(e instanceof JSONException)
logger.warning("Could not parse schema schema; JSONSerializer Error");
logger.log(Level.WARNING, "", e);
}
}
public static void main(String[] args) {
System.out.println("Testing Json Schema Validator");
JSONSchemaValidator validator = new JSONSchemaValidator();
//JSONObject schemaTest = validator.fetchJSONObj("http://192.168.1.105/schemas/object_stream_schema.json");
JSONObject schemaTest = validator.fetchJSONObj("http://192.168.1.105/schemas/protocols/join_schema.json");
validator.validate(null, schemaTest);
}
public static JSONObject fetchJSONObj(String docUrl){
try {
URL masterSchemaURL = new URL(docUrl);
BufferedReader in = new BufferedReader(new InputStreamReader(masterSchemaURL.openStream()));
String inputLine=null;
StringBuffer schemaBufDoc = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
schemaBufDoc = schemaBufDoc.append(inputLine).append("\n");
}
in.close();
if(JsonVerifier.isValidJsonValue(schemaBufDoc.toString())) {
return (JSONObject) JSONSerializer.toJSON(schemaBufDoc.toString());
}
else {
logger.warning("Could not parse schema schema");
return null;
}
} catch (Exception e){
if(e instanceof JSONException)
logger.warning("Could not parse schema schema; JSONSerializer Error");
logger.log(Level.WARNING, "", e);
return null;
}
}
public boolean validate(JSONObject jsonObj, JSONObject schemaObj) {
if (!validateSchema(schemaObj) || !validateObj(jsonObj, schemaObj)) {
for (int i=0; i<errorVec.size(); i++){
System.out.println(errorVec.elementAt(i));
}
return false;
}
return true;
}
private boolean validateObj(JSONObject jsonObj, JSONObject schemaObj){
if(jsonObj !=null){
JSONObject schemaPropsObj = schemaObj.getJSONObject("properties");
//1. check that all the properties are in the schema
LinkedHashSet attributes = new LinkedHashSet(jsonObj.keySet());
Iterator attrIterator = attributes.iterator();
boolean allAttrKnown = true;
while (attrIterator.hasNext() ){
Object attrNameObj = attrIterator.next();
if(JSONUtils.isString(attrNameObj)) {
String attrName = (String) attrNameObj;
allAttrKnown &= schemaPropsObj.has(attrName);
if(!allAttrKnown) {
addToErrorLog("Unknown attribute: " + attrName);
return false;
}
}else{
addToErrorLog("All keys must be strings");
return false;
}
}
//2. check if any mandatory properties are missing
//3. check that all the properties are within specification
}
return true;
}
private boolean validateSchema(JSONObject schemaObj){
LinkedHashSet keys = new LinkedHashSet(schemaObj.keySet());
Iterator keysIterator = keys.iterator();
//List values = schemaObj.values();
//this schema must be of type object or array
if(!schemaObj.has(TYPE) || (!schemaObj.getString(TYPE).equals("object") &&
!schemaObj.getString(TYPE).equals("array"))){
return false;
}
boolean anyErrors = false;
while (keysIterator.hasNext()){
anyErrors = anyErrors | checkProp(keysIterator.next(), schemaObj);
}
//print errors
if(anyErrors) {
for(int i=0; i<errorVec.size(); i++){
System.out.println(i+1 + ". " + (String)errorVec.elementAt(i));
}
}
return true;
}
private boolean checkProp(Object thisKey, JSONObject schemaObj) {
//Check that each key is a valid key type defined in the schema schema
JSONObject propsObj = masterSchema.getJSONObject(PROPS);
String keyStr = (String) thisKey;
if(!propsObj.has(keyStr)) {
String error = "Invalid key: " + keyStr;
errorVec.addElement(error);
}
//Check that key value adhere's to schema schema spec
Object propValue = propsObj.get(thisKey);
if(JSONUtils.isObject(propValue)) {
//System.out.println("object");
JSONObject typeDescObj = propsObj.getJSONObject(keyStr);
boolean proppropsOk = checkPropProps (thisKey, schemaObj, typeDescObj);
/*if(proppropsOk)
System.out.println("propprops ok");
else
System.out.println("propprops NOT ok");*/
return proppropsOk;
}else if (JSONUtils.isArray(propValue)) {
JSONArray typeDescJArray = propsObj.getJSONArray(keyStr);
//System.out.println("array");
}else if (JSONUtils.isString(propValue)) {
String typeDescStr = (String) propValue;
//System.out.println("string");
}
return true;
}
private boolean checkPropProps(Object thisKey, JSONObject schemaObj, JSONObject msPropValObj) {
try {
Object msPropType = msPropValObj.get(TYPE);
if(JSONUtils.isString(msPropType)){
//System.out.println("Type STRING");
if(msPropType.equals("string") && JSONUtils.isString(schemaObj.get(thisKey))){
return true;
}else if(msPropType.equals("boolean") && JSONUtils.isBoolean(schemaObj.get(thisKey))){
return true;
} else if(msPropType.equals("number") && JSONUtils.isNumber(schemaObj.get(thisKey))){
return true;
} else if(msPropType.equals("integer") ){
int thisInt = Integer.parseInt((String) schemaObj.get(thisKey));
return true;
} else if(msPropType.equals("object") && JSONUtils.isObject(schemaObj.get(thisKey))){
return true;
} else if (msPropType.equals("array") && JSONUtils.isArray(schemaObj.get(thisKey))){
return checkArrayProps(thisKey, schemaObj, msPropValObj);
} else if (msPropType.equals("null") && JSONUtils.isNull(schemaObj.get(thisKey))){
return true;
} else if(msPropType.equals("any")){
return true;
}
//add to error vector
addToErrorLog((String) thisKey +" must be type " + (String) msPropType);
return false;
}
else if (JSONUtils.isArray(msPropType)){
//System.out.println("Type ARRAY");
//JSONArray msTypeArray = msPropValObj.getJSONArray(TYPE);
JSONArray msPropTypeArray = (JSONArray) msPropType;
for(int i=0; i<msPropTypeArray.size(); i++){
Object thisPropType = msPropTypeArray.get(i);
if(JSONUtils.isString(thisPropType)){
if(thisPropType.equals("string") &&
JSONUtils.isString(schemaObj.get(thisKey))){
return true;
}else if(thisPropType.equals("boolean") &&
JSONUtils.isBoolean(schemaObj.get(thisKey))){
return true;
} else if(thisPropType.equals("number") &&
JSONUtils.isNumber(schemaObj.get(thisKey))){
return true;
} else if(thisPropType.equals("integer") ){
int thisInt = Integer.parseInt((String) schemaObj.get(thisKey));
return true;
} else if(thisPropType.equals("object") &&
JSONUtils.isObject(schemaObj.get(thisKey))){
return true;
} else if (thisPropType.equals("array") &&
JSONUtils.isArray(schemaObj.get(thisKey))){
return checkArrayProps(thisKey, schemaObj, msPropValObj);
} else if (thisPropType.equals("null") &&
JSONUtils.isNull(schemaObj.get(thisKey))){
return true;
} else if(thisPropType.equals("any")){
return true;
}
} else {
System.out.println("Master Schema Type Array element not a string");
}
}
//add to error vector
String errorStr = "";
for(int j=0; j<msPropTypeArray.size(); j++){
errorStr = errorStr +
(String) thisKey +" must be one of these types " + "["
+ (String) msPropTypeArray.get(j) + ", ";
}
errorStr = errorStr.substring(0, errorStr.length()-2) + "]";
return false;
}else {
System.out.println("Type OTHER");
}
return true;
} catch(NumberFormatException e){
//add to error vector
String thisError = (String) thisKey +" must be type integer";
addToErrorLog(thisError);
return false;
}
}
private void addToErrorLog(String error) {
errorVec.addElement(error);
}
private boolean checkArrayProps(Object key, JSONObject schemaObj, JSONObject propertyValObj){
if(propertyValObj.has("properties")) {
return true;
} else {
return true;
}
}
}