Package org.apache.shindig.social.opensocial.util

Source Code of org.apache.shindig.social.opensocial.util.ApiValidator

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.apache.shindig.social.opensocial.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.ScriptableObject;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
* A class that loads a feature set from features into a Javascript Parser to
* make the model available to validate JSON messages against.
*/
public class ApiValidator {

  private static final Log log = LogFactory.getLog(ApiValidator.class);
  private Context ctx;
  private ScriptableObject scope;

  /**
   * @param feature
   *                The name of the feature are eg "opensocial-reference", this
   *                is a classpath stub (not starting with /) where the location
   *                contains a feature.xml file.
   * @throws SAXException
   *                 if feature.xml is not parsable
   * @throws ParserConfigurationException
   *                 if the parsers are invalid
   * @throws IOException
   *                 if feature.xml or the javascript that represents the
   *                 feature is missing
   *
   */
  private ApiValidator(String feature) throws IOException,
      ParserConfigurationException, SAXException {
    ctx = Context.enter();
    scope = ctx.initStandardObjects();
    load(feature);
  }

  /**
   * Load the ApiValidator with no features, this avoids having features in the classpath
   * @throws IOException
   */
  public ApiValidator() throws IOException {
    ctx = Context.enter();
    scope = ctx.initStandardObjects();
  }

  /**
   * @param json
   *                The json to validate expected in a form { xyz: yyy } form
   * @param object
   *                The json Fields object specifying the structure of the json
   *                object, each field in this object contains the name of the
   *                json field in the json structure.
   * @param optionalFields
   *                If any of the fields that appear in the json structure are
   *                optional, then they should be defined in this parameter.
   * @param nullfields
   * @throws ApiValidatorExpcetion
   *                 if there is a problem validating the json
   * @return a map so string object pairs containing the fields at the top level
   *         of the json tree. Where these are native java objects, they will
   *         appear as native object. Complex json objects will appear as Rhino
   *         specific objects
   */
  public Map<String, Object> validate(String json, String object,
      String[] optionalFields, String[] nullfields)
      throws ApiValidatorExpcetion {

    /*
     * Object[] ids = ScriptableObject.getPropertyIds(scope); for (Object id :
     * ids) { Object o = ScriptableObject.getProperty(scope,
     * String.valueOf(id)); log.debug("ID is " + id + " class " + id.getClass() + "
     * is " + o); if (o instanceof ScriptableObject) {
     * listScriptable(String.valueOf(id), (ScriptableObject) o); } }
     */

    log.debug("Loading " + json);
    json = json.trim();
    if (!json.endsWith("}")) {
      json = json + "}";
    }
    if (!json.startsWith("{")) {
      json = "{" + json;
    }
    json = "( testingObject = " + json + " )";

    Object so = null;
    try {
      so = ctx.evaluateString(scope, json, "test json", 0, null);
    } catch (EvaluatorException ex) {
      log.error("Non parseable JSON " + json);
    }
    log.debug("Loaded " + so);

    ScriptableObject specification = getScriptableObject(object);
    log.debug("Looking for  " + object + " found " + specification);
    listScriptable(object, specification);
    Object[] fields = specification.getIds();
    String[] fieldNames = new String[fields.length];
    for (int i = 0; i < fields.length; i++) {
      Object fieldName = specification.get(String.valueOf(fields[i]), specification);
      fieldNames[i] = String.valueOf(fieldName);
    }

    return validateObject(so, fieldNames, optionalFields, nullfields);

  }

  /**
   * @param json
   *                The json to validate expected in a form { xyz: yyy } form
   * @param fieldNames
   *                An Array of field names that the oject should be tested against
   * @param optionalFields
   *                If any of the fields that appear in the json structure are
   *                optional, then they should be defined in this parameter.
   * @param nullfields
   * @throws ApiValidatorExpcetion
   *                 if there is a problem validating the json
   * @return a map so string object pairs containing the fields at the top level
   *         of the json tree. Where these are native java objects, they will
   *         appear as native object. Complex json objects will appear as Rhino
   *         specific objects
   */
  public Map<String, Object> validate(String json, String[] fieldNames,
      String[] optionalFields, String[] nullfields)
      throws ApiValidatorExpcetion {


    log.debug("Loading " + json);
    json = json.trim();
    if (!json.endsWith("}")) {
      json = json + "}";
    }
    if (!json.startsWith("{")) {
      json = "{" + json;
    }
    json = "( testingObject = " + json + " )";

    Object so = null;
    try {
      so = ctx.evaluateString(scope, json, "test json", 0, null);
    } catch (EvaluatorException ex) {
      log.error("Non parseable JSON " + json);
    }
    log.debug("Loaded " + so);


    return validateObject(so, fieldNames, optionalFields, nullfields);

  }

  /**
   * Validate an JSON Object extracted
   *
   * @param object
   * @param string
   * @param optional
   * @return
   * @throws ApiValidatorExpcetion
   */
  public Map<String, Object> validateObject(Object jsonObject, String[] fieldNames,
      String[] optionalFields, String[] nullFields)
      throws ApiValidatorExpcetion {
    Map<String, String> optional = Maps.newHashMap();
    for (String opt : optionalFields) {
      optional.put(opt, opt);
    }
    Map<String, String> nullf = Maps.newHashMap();
    for (String nf : nullFields) {
      nullf.put(nf, nf);
    }


    Map<String, Object> resultFields = Maps.newHashMap();

    if (jsonObject instanceof ScriptableObject) {
      ScriptableObject parsedJSONObject = (ScriptableObject) jsonObject;
      listScriptable("testingObject", parsedJSONObject);
      for (String fieldName : fieldNames) {
        Object o = parsedJSONObject.get(fieldName,
            parsedJSONObject);
        if (o == ScriptableObject.NOT_FOUND) {
          if (optional.containsKey(fieldName)) {
            log.warn("Missing Optional Field " + fieldName);
          } else if (!nullf.containsKey(fieldName)) {
            log.error("Missing Field " + fieldName);
            throw new ApiValidatorExpcetion("Missing Field " + fieldName);
          }
        } else {
          if (nullf.containsKey(fieldName)) {
            log.error("Field should have been null and was not");
          }
          if (o == null) {
            if (nullf.containsKey(fieldName)) {
              log.error("Null Fields has been serialized " + fieldName);
            }
            log.debug("Got a Null object for Field " + fieldName
                + " on json [[" + jsonObject + "]]");

          } else {

            log.debug("Got JSON Field  Field,"  + fieldName + " as "
                + o + " " + o.getClass());
          }
          resultFields.put(String.valueOf(fieldName), o);
        }
      }

    } else {
      throw new ApiValidatorExpcetion(
          "Parsing JSON resulted in invalid Javascript object, which was "
              + jsonObject + " JSON was [[" + jsonObject + "]]");
    }
    return resultFields;
  }

  /**
   * get an object from the json context and scope.
   *
   * @param object
   *                the name of the object specified as a path from the base
   *                object
   * @return the json object
   */
  private ScriptableObject getScriptableObject(String object) {
    String[] path = object.split("\\.");
    log.debug("Looking up " + object + " elements " + path.length);

    ScriptableObject s = scope;
    for (String pe : path) {
      log.debug("Looking up " + pe + " in " + s);
      s = (ScriptableObject) s.get(pe, s);
      log.debug("Looking for " + pe + " in found " + s);
    }
    return s;
  }

  /**
   * List a scriptable object at log debug level, constructors will not be
   * expanded as this loads to recursion.
   *
   * @param id
   *                The name of the object
   * @param scriptableObject
   *                the scriptable Object
   */
  private void listScriptable(String id, ScriptableObject scriptableObject) {
    log.debug("ID is Scriptable " + id);
    if (!id.endsWith("constructor")) {
      Object[] allIDs = scriptableObject.getAllIds();
      for (Object oid : allIDs) {
        log.debug(id + "." + oid);
        Object o = scriptableObject.get(String.valueOf(oid), scriptableObject);
        if (o instanceof ScriptableObject) {
          listScriptable(id + "." + String.valueOf(oid), (ScriptableObject) o);
        }
      }
    }
  }

  /**
   * Load a feature based on the spec
   *
   * @param spec
   *                The name of the location of the spec in the classpath, must
   *                not start with a '/' and must should contain a feature.xml
   *                file in the location
   * @throws IOException
   *                 If any of the resources cant be found
   * @throws ParserConfigurationException
   *                 If the parser has a problem being constructed
   * @throws SAXException
   *                 on a parse error on the features.xml
   */
  private void load(String spec) throws IOException, SAXException,
      ParserConfigurationException {

    List<String> scripts = getScripts(spec);

    List<Script> compiled = Lists.newArrayList();
    for (String script : scripts) {
      String scriptPath = spec + "/" + script;
      InputStream in = this.getClass().getClassLoader().getResourceAsStream(
          scriptPath);
      if (in == null) {
        in = this.getClass().getClassLoader().getResourceAsStream(
            "features/" + scriptPath);
        if (in == null) {
          throw new IOException("Cant load spec " + spec + " or features/"
              + spec + " from classpath");
        }
      }
      InputStreamReader reader = new InputStreamReader(in);
      Script compiledScript = ctx.compileReader(reader, spec, 0, null);
      compiled.add(compiledScript);
    }

    for (Script compiledScript : compiled) {
      compiledScript.exec(ctx, scope);
    }

  }

  /**
   * Add some javascript to the context, and execute it. If extra custom
   * javascript is wanted in the context or scope then this method will load it.
   *
   * @param javascript
   */
  public void addScript(String javascript) {
    Script compileScript = ctx.compileString(javascript, "AdditionalJS", 0,
        null);
    compileScript.exec(ctx, scope);
  }

  /**
   * Get an ordered list of javascript resources from a feature sets.
   *
   * @param spec
   *                The spec location
   * @return An ordered list of javascript resources, these are relative to
   *         specification file.
   * @throws IOException
   *                 If any of the resources can't be loaded.
   * @throws SAXException
   *                 Where the feature.xml file is not parsable
   * @throws ParserConfigurationException
   *                 where the parser can't be constructed.
   * @return An ordered list of script that need to be loaded and executed to
   *         make the feature available in the context.
   */
  private List<String> getScripts(String spec) throws SAXException,
      IOException, ParserConfigurationException {
    String features = spec + "/feature.xml";
    InputStream in = this.getClass().getClassLoader().getResourceAsStream(
        features);
    if (in == null) {
      in = this.getClass().getClassLoader().getResourceAsStream(
          "features/" + features);
      if (in == null) {
        throw new IOException("Cant find " + features + " or features/"
            + features + " in classpath ");
      }
    }
    DocumentBuilderFactory builderFactory = DocumentBuilderFactory
        .newInstance();
    DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
    Document doc = documentBuilder.parse(in);
    NodeList nl = doc.getElementsByTagName("script");
    List<String> scripts = Lists.newArrayList();
    for (int i = 0; i < nl.getLength(); i++) {
      Node scriptNode = nl.item(i);
      NamedNodeMap attributes = scriptNode.getAttributes();
      Node scriptAttr = attributes.getNamedItem("src");
      String script = scriptAttr.getNodeValue();
      scripts.add(script);
    }
    return scripts;
  }

  /**
   * @param nameJSON
   */
  public static void dump(Map<?, ?> nameJSON) {
    if (log.isDebugEnabled()) {
      for (Entry<?, ?> entry : nameJSON.entrySet()) {
        Object k = entry.getKey();
        Object o = entry.getValue();
        log.info("Key [" + k + "] value:[" + (o == null ? "null" : o + ":" + o.getClass()) + "]");
      }
    }
  }

}
TOP

Related Classes of org.apache.shindig.social.opensocial.util.ApiValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.