gt:             if (info.getName() != null) {
context: /*
   Copyright 2013 Red Hat, Inc. and/or its affiliates.
  
   This file is part of lightblue.
  
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
  
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  package com.redhat.lightblue.metadata.parser;
  
  import java.text.ParseException;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
  import java.util.Properties;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.fasterxml.jackson.databind.JsonNode;
  import com.google.common.collect.Sets;
  import com.redhat.lightblue.metadata.Access;
  import com.redhat.lightblue.metadata.ArrayElement;
  import com.redhat.lightblue.metadata.ArrayField;
  import com.redhat.lightblue.metadata.DataStore;
  import com.redhat.lightblue.metadata.EntityAccess;
  import com.redhat.lightblue.metadata.EntityConstraint;
  import com.redhat.lightblue.metadata.EntityInfo;
  import com.redhat.lightblue.metadata.EntityMetadata;
  import com.redhat.lightblue.metadata.EntitySchema;
  import com.redhat.lightblue.metadata.Enum;
  import com.redhat.lightblue.metadata.EnumValue;
  import com.redhat.lightblue.metadata.Enums;
  import com.redhat.lightblue.metadata.Field;
  import com.redhat.lightblue.metadata.FieldAccess;
  import com.redhat.lightblue.metadata.FieldConstraint;
  import com.redhat.lightblue.metadata.Fields;
  import com.redhat.lightblue.metadata.Hook;
  import com.redhat.lightblue.metadata.HookConfiguration;
  import com.redhat.lightblue.metadata.Hooks;
  import com.redhat.lightblue.metadata.Index;
  import com.redhat.lightblue.metadata.Indexes;
  import com.redhat.lightblue.metadata.MetadataConstants;
  import com.redhat.lightblue.metadata.MetadataStatus;
  import com.redhat.lightblue.metadata.ObjectArrayElement;
  import com.redhat.lightblue.metadata.ObjectField;
  import com.redhat.lightblue.metadata.ReferenceField;
  import com.redhat.lightblue.metadata.SimpleArrayElement;
  import com.redhat.lightblue.metadata.SimpleField;
  import com.redhat.lightblue.metadata.StatusChange;
  import com.redhat.lightblue.metadata.Type;
  import com.redhat.lightblue.metadata.TypeResolver;
  import com.redhat.lightblue.metadata.ValueGenerator;
  import com.redhat.lightblue.metadata.ValueGenerator.ValueGeneratorType;
  import com.redhat.lightblue.metadata.Version;
  import com.redhat.lightblue.metadata.types.ArrayType;
  import com.redhat.lightblue.metadata.types.DateType;
  import com.redhat.lightblue.metadata.types.ObjectType;
  import com.redhat.lightblue.metadata.types.ReferenceType;
  import com.redhat.lightblue.query.Projection;
  import com.redhat.lightblue.query.QueryExpression;
  import com.redhat.lightblue.query.Sort;
  import com.redhat.lightblue.query.SortKey;
  import com.redhat.lightblue.util.Error;
  import com.redhat.lightblue.util.Path;
  
  /**
   * Base class for converting metadata to/from json/bson and potentially other
   * formats represented as a tree.
   *
   * The metadata parser is thread safe.
   */
  public abstract class MetadataParser<T> {
  
      private static final String STR_ID = "_id";
      private static final String STR_NAME = "name";
      private static final String STR_VALUE = "value";
      private static final String STR_VERSION = "version";
      private static final String STR_DEFAULT_VERSION = "defaultVersion";
      private static final String STR_FIND = "find";
      private static final String STR_STATUS = "status";
      private static final String STR_CONSTRAINTS = "constraints";
      private static final String STR_ACCESS = "access";
      private static final String STR_UPDATE = "update";
      private static final String STR_FIELDS = "fields";
      private static final String STR_VALUES = "values";
      private static final String STR_ANNOTATED_VALUES = "annotatedValues";
      private static final String STR_ITEMS = "items";
      private static final String STR_UNIQUE = "unique";
      private static final String STR_INDEXES = "indexes";
      private static final String STR_VALUE_GENERATOR = "valueGenerator";
      private static final String STR_OVERWRITE = "overwrite";
      private static final String STR_TYPE = "type";
      private static final String STR_HOOKS = "hooks";
      private static final String STR_ENUMS = "enums";
      private static final String STR_ENTITY_INFO = "entityInfo";
      private static final String STR_SCHEMA = "schema";
      private static final String STR_DATASTORE = "datastore";
      private static final String STR_BACKEND = "backend";
      private static final String STR_EXTEND_VERSIONS = "extendVersions";
      private static final String STR_CHANGELOG = "changelog";
      private static final String STR_LOG = "log";
      private static final String STR_DATE = "date";
      private static final String STR_COMMENT = "comment";
      private static final String STR_DELETE = "delete";
      private static final String STR_INSERT = "insert";
      private static final String STR_ENTITY = "entity";
      private static final String STR_VERSION_VALUE = "versionValue";
      private static final String STR_PROJECTION = "projection";
      private static final String STR_ACTIONS = "actions";
      private static final String STR_QUERY = "query";
      private static final String STR_SORT = "sort";
      private static final String STR_ACTIVE = "active";
      private static final String STR_DEPRECATED = "deprecated";
      private static final String STR_DISABLED = "disabled";
      private static final String STR_CONFIGURATION = "configuration";
      private static final String STR_DESCRIPTION = "description";
  
      private static final Logger LOGGER = LoggerFactory.getLogger(MetadataParser.class);
      private static final Set<String> ENTITY_INFO_FIELDS = Sets.newHashSet(STR_ID, STR_NAME, STR_DEFAULT_VERSION, STR_INDEXES, STR_ENUMS, STR_HOOKS, STR_DATASTORE);
  
      private final Extensions<T> extensions;
      private final TypeResolver typeResolver;
  
      private interface ArrCb<S, D> {
          D parse(S child);
      }
  
      private final ArrCb<T, Index> parseIndex = new ArrCb<T, Index>() {
          @Override
          public Index parse(T child) {
              return parseIndex(child);
          }
      };
  
      private final ArrCb<T, Enum> parseEnum = new ArrCb<T, Enum>() {
          @Override
          public Enum parse(T child) {
              return parseEnum(child);
          }
      };
  
      private final ArrCb<T, Hook> parseHook = new ArrCb<T, Hook>() {
          @Override
          public Hook parse(T child) {
              return parseHook(child);
          }
      };
      
      public MetadataParser(Extensions<T> ex, TypeResolver typeResolver) {
          this.extensions = ex;
          this.typeResolver = typeResolver;
      }
  
      public DataStoreParser<T> getDataStoreParser(String backendName) {
          return extensions.getDataStoreParser(backendName);
      }
  
      public EntityConstraintParser<T> getEntityConstraintParser(String constraintName) {
          return extensions.getEntityConstraintParser(constraintName);
      }
  
      public FieldConstraintParser<T> getFieldConstraintParser(String constraintName) {
          return extensions.getFieldConstraintParser(constraintName);
      }
  
      public Extensions<T> getExtensions(){
          return this.extensions;
      }
  
      /**
       * Entry point for entity metadata parser. Expects an Object corresponding
       * to the EntityMetadata object.
       *
       * @throws ParseException
       */
      public EntityMetadata parseEntityMetadata(T object) {
          Error.push("parseEntityMetadata");
          try {
              EntityInfo info = parseEntityInfo(getRequiredObjectProperty(object, STR_ENTITY_INFO));
              EntitySchema schema = parseEntitySchema(getRequiredObjectProperty(object, STR_SCHEMA));
  
              EntityMetadata md = new EntityMetadata(info, schema);
  
              return md;
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      /**
       * Entry point for entity info parser. Expects an Object corresponding to
       * the EntityInfo object.
       *
       * @throws ParseException
       */
      public EntityInfo parseEntityInfo(T object) {
          Error.push("parseEntityInfo");
          try {
              String name = getRequiredStringProperty(object, STR_NAME);
  
              EntityInfo info = new EntityInfo(name);
  
              info.setDefaultVersion(getStringProperty(object, STR_DEFAULT_VERSION));
              info.getIndexes().setIndexes(parseArr(getObjectProperty(object, STR_INDEXES), parseIndex));
              info.getEnums().setEnums(parseArr(getObjectProperty(object, STR_ENUMS), parseEnum));
              info.getHooks().setHooks(parseArr(getObjectProperty(object, STR_HOOKS), parseHook));
  
              T backend = getRequiredObjectProperty(object, STR_DATASTORE);
              info.setDataStore(parseDataStore(backend));
  
              parsePropertyParser(object, info.getProperties());
  
              return info;
          } finally {
              Error.pop();
          }
      }
  
      public void parsePropertyParser(T object, Map<String, Object> properties) {
          Error.push("parsePropertyParser");
          try {
  
              Set<String> propertyFields = findFieldsNotIn(object, ENTITY_INFO_FIELDS);
              for (String property : propertyFields) {
                  PropertyParser p = extensions.getPropertyParser(property);
                  if (p != null) {
                      p.parseProperty(this, property, properties, getObjectProperty(object, property));
                  }
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      private <I> List<I> parseArr(T object, ArrCb<T, I> cb) {
          Error.push("parseArray");
          try {
              if (object != null) {
                  List<T> children = getObjectList(object);
  
                  List<I> list = new ArrayList<>();
  
                  for (T child : children) {
                      list.add(cb.parse(child));
                  }
  
                  return list;
              } else {
                  return null;
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      public Index parseIndex(T object) {
          Error.push("parseIndex");
          try {
              if (object != null) {
                  Index index = new Index();
  
                  String name = getStringProperty(object, STR_NAME);
                  Object unique = getValueProperty(object, STR_UNIQUE);
                  List<T> fields = getObjectList(object, STR_FIELDS);
  
                  if (null != name) {
                      index.setName(name);
                  }
  
                  if (null != unique) {
                      index.setUnique(Boolean.parseBoolean(unique.toString()));
                  }
  
                  if (null != fields && !fields.isEmpty()) {
                      List<SortKey> f = new ArrayList<>();
  
                      for (T s : fields) {
                          String fld = getRequiredStringProperty(s, "field");
                          String dir = getStringProperty(s, "dir");
                          if (dir == null) {
                              dir = "$asc";
                          }
                          SortKey sort = new SortKey(new Path(fld), "$desc".equals(dir));
                          f.add(sort);
                      }
                      index.setFields(f);
                  } else {
                      throw Error.get(MetadataConstants.ERR_PARSE_MISSING_ELEMENT, STR_FIELDS);
                  }
  
                  return index;
              } else {
                  return null;
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      public Enum parseEnum(T object) {
          Error.push("parseEnum");
          try {
              if (object != null) {
                  String name = getStringProperty(object, STR_NAME);
                  if (name == null) {
                      throw Error.get(MetadataConstants.ERR_PARSE_MISSING_ELEMENT, STR_NAME);
                  }
                  Enum e = new Enum(name);
  
                  List<String> values = getStringList(object, STR_VALUES);
                  List<T> annotatedValues = getObjectList(object, STR_ANNOTATED_VALUES);
  
                  Set<EnumValue> enumValues = new HashSet<EnumValue>();
                  if(annotatedValues != null){
                      for(T value : annotatedValues){
                          EnumValue enumValue = new EnumValue();
                          enumValue.setName(getRequiredStringProperty(value, STR_NAME));
                          enumValue.setDescription(getRequiredStringProperty(value, STR_DESCRIPTION));
                          enumValues.add(enumValue);
                      }
                  }
                  else if(values != null){
                      for(String string : values){
                          enumValues.add(new EnumValue(string, null));
                      }
                  }
  
                  if(enumValues.isEmpty()){
                      throw Error.get(MetadataConstants.ERR_PARSE_MISSING_ELEMENT, STR_VALUES);
                  }
  
                  e.setValues(enumValues);
                  return e;
              } else {
                  return null;
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      public Hook parseHook(T object) {
          Error.push("parseHook");
          try {
              if (object != null) {
                  String name = getStringProperty(object, STR_NAME);
                  if (name == null) {
                      throw Error.get(MetadataConstants.ERR_PARSE_MISSING_ELEMENT, STR_NAME);
                  }
                  Hook hook = new Hook(name);
                  Projection projection=getProjection(object, STR_PROJECTION);
                  if (projection != null) {
                      hook.setProjection(projection);
                  }
                  List<String> values = getStringList(object, STR_ACTIONS);
                  if (values != null) {
                      hook.setInsert(values.contains(STR_INSERT));
                      hook.setUpdate(values.contains(STR_UPDATE));
                      hook.setDelete(values.contains(STR_DELETE));
                      hook.setFind(values.contains(STR_FIND));
                  }
                  T cfg = getObjectProperty(object, STR_CONFIGURATION);
                  if (cfg != null) {
                      HookConfigurationParser<T> parser = extensions.getHookConfigurationParser(name);
                      if (parser == null) {
                          throw Error.get(MetadataConstants.ERR_INVALID_HOOK, name);
                      }
                      hook.setConfiguration(parser.parse(name, this, cfg));
                  }
                  return hook;
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
          return null;
      }
  
      public ValueGenerator parseValueGenerator(T object) {
          Error.push("parseValueGenerator");
          try {
              if (object != null) {
                  String type = getStringProperty(object, STR_TYPE);
                  if (type == null) {
                      throw Error.get(MetadataConstants.ERR_PARSE_MISSING_ELEMENT, STR_TYPE);
                  }
  
                  ValueGenerator valueGenerator = new ValueGenerator(ValueGenerator.ValueGeneratorType.valueOf(type));
                  Object x=getValueProperty(object,STR_OVERWRITE);
                  if(x instanceof Boolean)
                      valueGenerator.setOverwrite((Boolean)x);
  
                  T config=getObjectProperty(object,STR_CONFIGURATION);
                  if(config!=null) {
                      Set<String> names=getChildNames(config);
                      for(String name:names) {
                          Object value=getValueProperty(config,name);
                          valueGenerator.getProperties().put(name, value);
                      }
                  }
                  
                  return valueGenerator;
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
          return null;
      }
  
      /**
       * Entry point for entity schema parser. Expects an Object corresponding to
       * the EntitySchema object.
       *
       * @throws ParseException
       */
      public EntitySchema parseEntitySchema(T object) {
          Error.push("parseEntitySchema");
          try {
              String name = getRequiredStringProperty(object, STR_NAME);
  
              EntitySchema schema = new EntitySchema(name);
              T version = getRequiredObjectProperty(object, STR_VERSION);
              schema.setVersion(parseVersion(version));
  
              T status = getRequiredObjectProperty(object, STR_STATUS);
              parseStatus(schema, status);
  
              T access = getObjectProperty(object, STR_ACCESS);
              if (access != null) {
                  parseEntityAccess(schema.getAccess(), access);
              }
  
              T fields = getRequiredObjectProperty(object, STR_FIELDS);
              parseFields(schema.getFields(), fields);
  
              List<T> constraints = getObjectList(object, STR_CONSTRAINTS);
              parseEntityConstraints(schema, constraints);
  
              parsePropertyParser(object, schema.getProperties());
  
              return schema;
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      /**
       * Parses metadata version
       *
       * @param object Object corresponding to the version element
       *
       * @return The parsed version
       */
      public Version parseVersion(T object) {
          Error.push(STR_VERSION);
          try {
              if (object != null) {
                  Version v = new Version();
                  v.setValue(getRequiredStringProperty(object, STR_VALUE));
                  List<String> l = getStringList(object, STR_EXTEND_VERSIONS);
                  if (l != null) {
                      v.setExtendsVersions(l.toArray(new String[l.size()]));
                  }
                  v.setChangelog(getRequiredStringProperty(object, STR_CHANGELOG));
                  return v;
              } else {
                  return null;
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      /**
       * Parses metadata status, and populates metadata
       *
       * @throws ParseException
       */
      public void parseStatus(EntitySchema schema, T object) {
          Error.push(STR_STATUS);
          try {
              schema.setStatus(statusFromString(getRequiredStringProperty(object, STR_VALUE)));
              List<T> logList = getObjectList(object, STR_LOG);
              List<StatusChange> list = new ArrayList<>();
              if (logList != null) {
                  for (T log : logList) {
                      StatusChange item = new StatusChange();
                      String d = getRequiredStringProperty(log, STR_DATE);
                      try {
                          item.setDate(DateType.getDateFormat().parse(d));
                      } catch (ParseException e) {
                          throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, d);
                      }
                      item.setStatus(statusFromString(getRequiredStringProperty(log, STR_VALUE)));
                      item.setComment(getStringProperty(log, STR_COMMENT));
                      list.add(item);
                  }
                  schema.setStatusChangeLog(list);
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      /**
       * Parses metadata entity access
       *
       * @param access The entity access object to be initialized
       * @param object The object corresponding to the entity access element
       */
      public void parseEntityAccess(EntityAccess access,
              T object) {
          Error.push(STR_ACCESS);
          try {
              if (object != null) {
                  parseAccess(access.getFind(), getStringList(object, STR_FIND));
                  parseAccess(access.getUpdate(), getStringList(object, STR_UPDATE));
                  parseAccess(access.getDelete(), getStringList(object, STR_DELETE));
                  parseAccess(access.getInsert(), getStringList(object, STR_INSERT));
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      /**
       * Parses entity constraints using the registered entity constraint parsers.
       *
       * Entity constraints are an object array where each object contains only
       * one field, the constraint name. The constraint data can be a simple
       * value, an array, or an object.
       */
      public void parseEntityConstraints(EntitySchema schema,
              List<T> constraintList) {
          if (constraintList != null) {
              List<EntityConstraint> entityConstraintList = new ArrayList<>();
              for (T x : constraintList) {
                  // The constraint object must contain a single field
                  String name = getSingleFieldName(x, MetadataConstants.ERR_INVALID_CONSTRAINT);
                  Error.push(name);
                  try {
                      EntityConstraintParser<T> parser = getEntityConstraintParser(name);
                      if (parser == null) {
                          throw Error.get(MetadataConstants.ERR_INVALID_CONSTRAINT, name);
                      }
                      EntityConstraint constraint = parser.parse(name, this, x);
                      entityConstraintList.add(constraint);
                  } catch (Error e) {
                      // rethrow lightblue error
                      throw e;
                  } catch (Exception e) {
                      // throw new Error (preserves current error context)
                      LOGGER.error(e.getMessage(), e);
                      throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
                  } finally {
                      Error.pop();
                  }
              }
              if (!entityConstraintList.isEmpty()) {
                  schema.setConstraints(entityConstraintList);
              }
          }
      }
  
      public List<FieldConstraint> parseFieldConstraints(T fieldConstraints) {
          List<FieldConstraint> constraints = new ArrayList<>();
          if (fieldConstraints != null) {
              // The constraint object must contain a single field
              Set<String> childNames = getChildNames(fieldConstraints);
              for (String name : childNames) {
                  Error.push(name);
                  try {
                      FieldConstraintParser<T> parser = getFieldConstraintParser(name);
                      if (parser == null) {
                          throw Error.get(MetadataConstants.ERR_INVALID_CONSTRAINT, name);
                      }
                      // for each FieldConstraint call parse on the parent object
                      FieldConstraint constraint = parser.parse(name, this, fieldConstraints);
                      constraints.add(constraint);
                  } catch (Error e) {
                      // rethrow lightblue error
                      throw e;
                  } catch (Exception e) {
                      // throw new Error (preserves current error context)
                      LOGGER.error(e.getMessage(), e);
                      throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
                  } finally {
                      Error.pop();
                  }
              }
          }
          return constraints;
      }
  
      public void parseFieldConstraints(Field field,
                                        T fieldConstraints) {
          List<FieldConstraint> constraints=parseFieldConstraints(fieldConstraints);
          if (!constraints.isEmpty()) {
              field.setConstraints(constraints);
          }
      }
  
      /**
       * Parses field access
       *
       * @param access The field access object to be initialized
       * @param object The object corresponding to the field access element
       */
      public void parseFieldAccess(FieldAccess access,
              T object) {
          Error.push(STR_ACCESS);
          try {
              if (object != null) {
                  parseAccess(access.getFind(), getStringList(object, STR_FIND));
                  parseAccess(access.getUpdate(), getStringList(object, STR_UPDATE));
                  parseAccess(access.getInsert(), getStringList(object, STR_INSERT));
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      private void parseAccess(Access access, List<String> roles) {
          if (roles != null) {
              access.setRoles(roles);
          }
      }
  
      /**
       * Parses and initializes fields
       *
       * @param fields The destination object to be initialized
       * @param object The object corresponding to the fields element
       */
      public void parseFields(Fields fields, T object) {
          Error.push(STR_FIELDS);
          try {
              if (object != null) {
                  Set<String> names = getChildNames(object);
                  for (String name : names) {
                      T fieldObject = getObjectProperty(object, name);
                      Field field = parseField(name, fieldObject);
                      fields.addNew(field);
                  }
              }
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      /**
       * Parses a backend using a registered backend parser
       *
       * @param object The object for the backend element. The object must contain
       * only one object field whose name is used to resolve the backend parser
       *
       * @return The parsed backend. Returns null if object is null.
       */
      public DataStore parseDataStore(T object) {
          if (object != null) {
              LOGGER.debug("parseDataStore {}", object);
              String name = getRequiredStringProperty(object, STR_BACKEND);
              LOGGER.debug("Backend:{}", name);
              DataStoreParser<T> p = getDataStoreParser(name);
              LOGGER.debug("parser: {}", p);
              if (p == null) {
                  throw Error.get(MetadataConstants.ERR_UNKNOWN_BACKEND, name);
              }
              return p.parse(name, this, object);
          } else {
              return null;
          }
      }
  
      /**
       * Returns the single field name contained in the object. If the object
       * contains more fields or no fields, throws an error with the given error
       * code.
       */
      private String getSingleFieldName(T object, String errorCode) {
          Set<String> names = getChildNames(object);
          if (names.size() != 1) {
              throw Error.get(errorCode, names.toString());
          }
          return names.iterator().next();
      }
  
      private Field parseField(String name, T object) {
          Field field;
          Error.push(name);
          try {
              if (object != null) {
                  String type = getRequiredStringProperty(object, STR_TYPE);
                  if (type.equals(ArrayType.TYPE.getName())) {
                      field = parseArrayField(name, object);
                  } else if (type.equals(ObjectType.TYPE.getName())) {
                      field = parseObjectField(name, object);
                  } else if (type.equals(ReferenceType.TYPE.getName())) {
                      field = parseReferenceField(name, object);
                  } else {
                      field = parseSimpleField(name, type, object);
                  }
                  parseFieldAccess(field.getAccess(),
                          getObjectProperty(object, STR_ACCESS));
                  parseFieldConstraints(field,
                          getObjectProperty(object, STR_CONSTRAINTS));
  
                  parsePropertyParser(object, field.getProperties());
              } else {
                  field = null;
              }
              return field;
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      private SimpleField parseSimpleField(String name,
                                           String type,
                                           T object) {
          SimpleField field = new SimpleField(name);
          Type t = typeResolver.getType(type);
          if (t == null) {
              throw Error.get(MetadataConstants.ERR_INVALID_TYPE, type);
          }
          field.setType(t);
  
          T vg=getObjectProperty(object,STR_VALUE_GENERATOR);
          if(vg!=null)
              field.setValueGenerator(parseValueGenerator(vg));
          
          return field;
      }
  
      private ReferenceField parseReferenceField(String name,
              T object) {
          ReferenceField field = new ReferenceField(name);
          field.setEntityName(getRequiredStringProperty(object, STR_ENTITY));
          field.setVersionValue(getRequiredStringProperty(object, STR_VERSION_VALUE));
          field.setProjection(getProjection(object, STR_PROJECTION));
          field.setQuery(getQuery(object, STR_QUERY));
          field.setSort(getSort(object, STR_SORT));
  
          return field;
      }
  
      private ObjectField parseObjectField(String name, T object) {
          ObjectField field = new ObjectField(name);
          T fields = getRequiredObjectProperty(object, STR_FIELDS);
          parseFields(field.getFields(), fields);
          return field;
      }
  
      private ArrayField parseArrayField(String name, T object) {
          ArrayField field = new ArrayField(name);
          T items = getRequiredObjectProperty(object, STR_ITEMS);
          field.setElement(parseArrayItem(items));
          return field;
      }
  
      private ArrayElement parseArrayItem(T items) {
          String type = getRequiredStringProperty(items, STR_TYPE);
  
          if (type.equals(ObjectType.TYPE.getName())) {
              T fields = getRequiredObjectProperty(items, STR_FIELDS);
              ObjectArrayElement ret = new ObjectArrayElement();
              ret.setType(ObjectType.TYPE);
              parseFields(ret.getFields(), fields);
              return ret;
          } else if (type.equals(ArrayType.TYPE.getName())
                  || type.equals(ReferenceType.TYPE.getName())) {
              throw Error.get(MetadataConstants.ERR_INVALID_ARRAY_ELEMENT_TYPE, type);
          } else {
              SimpleArrayElement ret = new SimpleArrayElement();
              Type t = typeResolver.getType(type);
              if (t == null) {
                  throw Error.get(MetadataConstants.ERR_INVALID_TYPE, type);
              }
              ret.setType(t);
              List<FieldConstraint> constraints=parseFieldConstraints(getObjectProperty(items, STR_CONSTRAINTS));
              if(constraints!=null&&!constraints.isEmpty())
                  ret.setConstraints(constraints);
              return ret;
          }
      }
  
      /**
       * Converts the entity metadata to T
       */
      public T convert(EntityMetadata md) {
          Error.push("convert[metadata]");
          try {
              T ret = newNode();
              putObject(ret, STR_ENTITY_INFO, convert(md.getEntityInfo()));
              putObject(ret, STR_SCHEMA, convert(md.getEntitySchema()));
              return ret;
          } catch (Error e) {
              // rethrow lightblue error
              throw e;
          } catch (Exception e) {
              // throw new Error (preserves current error context)
              LOGGER.error(e.getMessage(), e);
              throw Error.get(MetadataConstants.ERR_ILL_FORMED_METADATA, e.getMessage());
          } finally {
              Error.pop();
          }
      }
  
      /**
       * Converts the entity metadata to T
       */
      public T convert(EntityInfo info) {
          Error.push("convert[info]");
          try {
              T ret = newNode();
  