package com.breeze.jpa; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Member; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import javax.persistence.Column; import javax.persistence.EntityManagerFactory; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.Attribute.PersistentAttributeType; import javax.persistence.metamodel.Type.PersistenceType; import javax.persistence.metamodel.Bindable; import javax.persistence.metamodel.EmbeddableType; import javax.persistence.metamodel.IdentifiableType; import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SingularAttribute; import com.breeze.metadata.Metadata; import com.breeze.metadata.RawMetadata; /** * Builds a data structure containing the metadata required by Breeze. * * @see <a href="http://breeze.github.io/doc-js/metadata-schema.html">Breeze Metadata Doc</a> * @author IdeaBlade * */ @SuppressWarnings("rawtypes") public class JPAMetadata extends Metadata { private EntityManagerFactory _emFactory; private RawMetadata _rawMetadata; private List<HashMap<String, Object>> _typeList; private HashMap<String, Object> _resourceMap; private HashSet<String> _typeNames; private HashMap<String, String> _fkMap; public JPAMetadata(EntityManagerFactory emFactory) { _emFactory = emFactory; } /** * Build the raw Breeze metadata. This will then get wrapped with a strongly typed wrapper. The internal * rawMetadata can be converted to JSON and sent to the Breeze client. */ @Override public RawMetadata buildRawMetadata() { initMap(); Set<ManagedType<?>> classMeta = _emFactory.getMetamodel().getManagedTypes(); // classMeta.clear(); // TODO test only // classMeta.add(_emFactory.getMetamodel().entity(northwind.jpamodel.Employee.class)); for (ManagedType<?> meta : classMeta) { addClass(meta); } return _rawMetadata; } void initMap() { _rawMetadata = new RawMetadata(); _typeList = new ArrayList<HashMap<String, Object>>(); _typeNames = new HashSet<String>(); _resourceMap = new HashMap<String, Object>(); _fkMap = new HashMap<String, String>(); _rawMetadata.put("localQueryComparisonOptions", "caseInsensitiveSQL"); _rawMetadata.put("structuralTypes", _typeList); _rawMetadata.put("resourceEntityTypeMap", _resourceMap); _rawMetadata.foreignKeyMap = _fkMap; } /** * Add the metadata for an entity or mapped superclass. * Embeddables are skipped, and only added when they are the property of an entity. * * @param meta */ void addClass(ManagedType<?> meta) { if (!(meta instanceof IdentifiableType)) return; // skip embeddable types until they are encountered via an entity Class type = meta.getJavaType(); String classKey = getEntityTypeName(type); HashMap<String, Object> cmap = new LinkedHashMap<String, Object>(); _typeList.add(cmap); cmap.put("shortName", type.getSimpleName()); cmap.put("namespace", type.getPackage().getName()); IdentifiableType<?> idmeta = (IdentifiableType) meta; IdentifiableType superMeta = idmeta.getSupertype(); if (superMeta != null) { Class superClass = superMeta.getJavaType(); cmap.put("baseTypeName", getEntityTypeName(superClass)); } String genType = "None"; if (idmeta.hasSingleIdAttribute()) { SingularAttribute<?,?> idAttr = getSingleIdAttribute(idmeta); Member member = idAttr.getJavaMember(); GeneratedValue genValueAnn = ((AnnotatedElement)member).getAnnotation(GeneratedValue.class); if (genValueAnn != null) { // String generator = genValueAnn.generator(); GenerationType strategy = genValueAnn.strategy(); if (strategy == GenerationType.SEQUENCE || strategy == GenerationType.TABLE) genType = "KeyGenerator"; else if (strategy == GenerationType.IDENTITY || strategy == GenerationType.AUTO) genType = "Identity"; // not sure what to do about AUTO cmap.put("autoGeneratedKeyType", genType); } } String resourceName = pluralize(type.getSimpleName()); // TODO find the real name cmap.put("defaultResourceName", resourceName); _resourceMap.put(resourceName, classKey); ArrayList<HashMap<String, Object>> dataArrayList = new ArrayList<HashMap<String, Object>>(); cmap.put("dataProperties", dataArrayList); ArrayList<HashMap<String, Object>> navArrayList = new ArrayList<HashMap<String, Object>>(); cmap.put("navigationProperties", navArrayList); addClassProperties(meta, dataArrayList, navArrayList); } /** * Add the properties for an entity. * @param meta - metamodel for a managed type * @param dataArrayList - will be populated with the data properties of the entity * @param navArrayList - will be populated with the navigation properties of the entity */ void addClassProperties(ManagedType<?> meta, ArrayList<HashMap<String, Object>> dataArrayList, ArrayList<HashMap<String, Object>> navArrayList) { for (SingularAttribute<?,?> attr : meta.getDeclaredSingularAttributes()) { String propName = attr.getName(); PersistentAttributeType attribType = attr.getPersistentAttributeType(); if (attribType == PersistentAttributeType.EMBEDDED) { // complex type if (attr.isId()) { // need to map the fields individually EmbeddableType<?> bed = _emFactory.getMetamodel().embeddable(attr.getJavaType()); for (Attribute<?,?> battr : bed.getAttributes()) { PersistentAttributeType attrType = attr.getPersistentAttributeType(); if (attrType == PersistentAttributeType.MANY_TO_ONE) { // association type HashMap<String, Object> assProp = makeAssociationProperty(battr, dataArrayList, true); navArrayList.add(assProp); } else { // data property SingularAttribute sbattr = (SingularAttribute) battr; HashMap<String, Object> dmap = makeDataProperty(battr.getName(), sbattr, true, false); dataArrayList.add(0, dmap); } } } else { String complexTypeName = addComponent(attr); HashMap<String, Object> compMap = new HashMap<String, Object>(); compMap.put("nameOnServer", propName); compMap.put("complexTypeName", complexTypeName); compMap.put("isNullable", attr.isOptional()); dataArrayList.add(compMap); } } else if (attribType == PersistentAttributeType.BASIC) { // data property HashMap<String, Object> dmap = makeDataProperty(propName, attr, attr.isId(), attr.isVersion()); if (attr.isId()) dataArrayList.add(0, dmap); else dataArrayList.add(dmap); } else { if (!attr.isAssociation()){ throw new RuntimeException("Can't handle attribute " + attr); } // Skip associations until below } } // We do the collection properties after the data properties, so we can // do the foreign key lookups for (Attribute<?,?> attr : meta.getDeclaredAttributes()) { if (attr.isAssociation()) { HashMap<String, Object> assProp = makeAssociationProperty(attr, dataArrayList, true); navArrayList.add(assProp); } else { // should have been handled above } } } /** Get the id attribute for an entity, or null if it doesn't have one */ SingularAttribute<?,?> getSingleIdAttribute(IdentifiableType<?> type) { if (type.hasSingleIdAttribute()) { // This throws when id is a primitive //SingularAttribute<?,?> idAttr = idmeta.getId(idType.getJavaType()); for (SingularAttribute<?,?> testAttr : type.getDeclaredSingularAttributes()) { if (testAttr.isId()) { return testAttr; } } } return null; } boolean contains(int[] array, int x) { for (int j = 0; j < array.length; j++) { if (array[j] == x) return true; } return false; } boolean contains(String[] array, String x) { for (int j = 0; j < array.length; j++) { if (array[j].equals(x)) return true; } return false; } /** * Adds an embeddable type definition * * @param sattr - The embeddable type metamodel */ String addComponent(SingularAttribute sattr) { Class<?> type = sattr.getJavaType(); // "Location:#com.breeze.model" String classKey = getEntityTypeName(type); if (_typeNames.contains(classKey)) { // Only add a complex type definition once. return classKey; } HashMap<String, Object> cmap = new LinkedHashMap<String, Object>(); _typeList.add(0, cmap); _typeNames.add(classKey); cmap.put("shortName", type.getSimpleName()); cmap.put("namespace", type.getPackage().getName()); cmap.put("isComplexType", true); ArrayList<HashMap<String, Object>> dataArrayList = new ArrayList<HashMap<String, Object>>(); cmap.put("dataProperties", dataArrayList); EmbeddableType<?> bed = _emFactory.getMetamodel().embeddable(type); for (Attribute<?,?> attrib : bed.getAttributes()) { PersistentAttributeType attrType = attrib.getPersistentAttributeType(); if (!(attrib instanceof SingularAttribute)) { throw new RuntimeException("Collections not supported in complex types"); } SingularAttribute cattr = (SingularAttribute) attrib; if (attrType == PersistentAttributeType.EMBEDDED) { // nested complex type String complexTypeName = addComponent(cattr); HashMap<String, Object> compMap = new HashMap<String, Object>(); compMap.put("nameOnServer", attrib.getName()); compMap.put("complexTypeName", complexTypeName); compMap.put("isNullable", cattr.isOptional()); dataArrayList.add(compMap); } else { // data property HashMap<String, Object> dmap = makeDataProperty(cattr.getName(), cattr, false, false); dataArrayList.add(dmap); } } return classKey; } /** * Make data property metadata for the entity * * @param propName - name of the property (attribute, in JPA parlance) on the server * @param sattr - metamodel for the property * @param isKey - true if this property is part of the key for the entity * @param isVersion - true if this property contains the version of the entity (for a * concurrency strategy) * @return data property definition */ private HashMap<String, Object> makeDataProperty(String propName, SingularAttribute sattr, boolean isKey, boolean isVersion) { Class type = sattr.getJavaType(); String newType = BreezeTypeMap.get(type.getSimpleName().toLowerCase()); String typeName = newType != null ? newType : type.getSimpleName(); Member member = sattr.getJavaMember(); HashMap<String, Object> dmap = new LinkedHashMap<String, Object>(); dmap.put("nameOnServer", propName); Enumerated numer = ((AnnotatedElement)member).getAnnotation(Enumerated.class); if (numer != null) { if (numer.value() == EnumType.STRING) { typeName = "String"; } else { typeName = "Byte"; } dmap.put("enumType", type.getSimpleName()); } dmap.put("dataType", typeName); dmap.put("isNullable", sattr.isOptional() && !type.isPrimitive()); // isNullable=true is the breeze default if (isKey) { dmap.put("isPartOfKey", true); } if (isVersion) { dmap.put("concurrencyMode", "Fixed"); } ArrayList<HashMap<String, String>> validators = new ArrayList<HashMap<String, String>>(); if (!sattr.isOptional()) { validators.add(newMap("name", "required")); } Column col = ((AnnotatedElement)member).getAnnotation(Column.class); if (col != null) { if (col.length() != 0) { dmap.put("maxLength", col.length()); validators.add(newMap("maxLength", Integer.toString(col.length()), "name", "maxLength")); } if (col.precision() != 0) { dmap.put("precision", col.precision()); } if (col.scale() != 0) { dmap.put("scale", col.scale()); } } String validationType = ValidationTypeMap.get(typeName); if (validationType != null) { validators.add(newMap("name", validationType)); } if (!validators.isEmpty()) dmap.put("validators", validators); return dmap; } /** * Make a HashMap populated with the given key and value. * @param key * @param value * @return */ static HashMap<String, String> newMap(String key, String value) { HashMap<String, String> map = new HashMap<String, String>(); map.put(key, value); return map; } static HashMap<String, String> newMap(String key, String value, String key2, String value2) { HashMap<String, String> map = newMap(key, value); map.put(key2, value2); return map; } /** * Make association property metadata for the entity. Also populates the _fkMap which is used * for related-entity fixup when saving. * * @param attr - the property (JPA attribute) metamodel * @param dataProperties - data properties already added for the same entity. Used for finding foreign keys for navigation properties. * @param isKey - whether this property is part of the key * @return association property definition */ private HashMap<String, Object> makeAssociationProperty(Attribute attr, ArrayList<HashMap<String, Object>> dataProperties, boolean isKey) { HashMap<String, Object> nmap = new LinkedHashMap<String, Object>(); String propName = attr.getName(); nmap.put("nameOnServer", propName); Class relatedEntityType = getEntityType(attr); nmap.put("entityTypeName", getEntityTypeName(relatedEntityType)); nmap.put("isScalar", !attr.isCollection()); // the associationName must be the same at both ends of the association. Class containingType = attr.getDeclaringType().getJavaType(); String[] columnNames = getColumnNames(attr); nmap.put("associationName", getAssociationName(containingType.getSimpleName(), relatedEntityType.getSimpleName(), columnNames)); Member member = attr.getJavaMember(); String[] fkNames = null; if (attr.isCollection()) { javax.persistence.metamodel.Type elementType = ((PluralAttribute) attr).getElementType(); if (elementType.getPersistenceType() != PersistenceType.ENTITY) { throw new RuntimeException("Collection association " + attr + " has elementType " + elementType); } fkNames = getPropertyNamesForColumns((javax.persistence.metamodel.EntityType<?>) elementType, columnNames); if (fkNames != null) { nmap.put("invForeignKeyNamesOnServer", fkNames); } } else { // Not a collection type - a many-to-one or one-to-one association String entityRelationship = containingType.getName() + '.' + propName; fkNames = getPropertyNamesForColumns((javax.persistence.metamodel.EntityType<?>) attr.getDeclaringType(), columnNames); if (fkNames != null) { if (attr.getPersistentAttributeType() == PersistentAttributeType.MANY_TO_ONE) { nmap.put("foreignKeyNamesOnServer", fkNames); } else if (attr.getPersistentAttributeType() == PersistentAttributeType.ONE_TO_ONE) { OneToOne oto = ((AnnotatedElement)member).getAnnotation(OneToOne.class); if (oto != null && !isEmpty(oto.mappedBy())) { nmap.put("invForeignKeyNamesOnServer", fkNames); } else { nmap.put("foreignKeyNamesOnServer", fkNames); } } // For many-to-one and one-to-one associations, save the relationship in _fkMap // for re-establishing relationships during save _fkMap.put(entityRelationship, catColumnNames(fkNames, ',')); if (isKey) { for (String fkName : fkNames) { HashMap<String, Object> relatedDataProperty = findPropertyByName(dataProperties, fkName); if (relatedDataProperty == null) { nmap.put("ERROR", "Could not find matching data property for " + entityRelationship + ", fkNames=" + fkNames); throw new IllegalArgumentException("Could not find matching data property for " + entityRelationship + ", fkNames=" + Arrays.asList(fkNames)); } else if (!relatedDataProperty.containsKey("isPartOfKey")) { relatedDataProperty.put("isPartOfKey", true); } } } } else if (fkNames == null) { nmap.put("foreignKeyNamesOnServer", columnNames); nmap.put("ERROR", "Could not find matching fk for property " + entityRelationship); _fkMap.put(entityRelationship, catColumnNames(columnNames, ',')); throw new IllegalArgumentException("Could not find matching fk for property " + entityRelationship); } } return nmap; } /** * @return the type name in the form "Order:#northwind.model" */ String getEntityTypeName(Class clazz) { // return clazz.getName(); return clazz.getSimpleName() + ":#" + clazz.getPackage().getName(); } /** * Get the column names for a given property as an array of unbracketed, * lowercase names. For a collection property, the column name is the inverse foreign key (i.e. * the column on the other table that points back to the persister's table) */ String[] getColumnNames(Attribute attr) { List<String> names = getAttributeColumnNames(attr); return unBracket(names.toArray(new String[names.size()])); } /** Get the column names for the ID attribute of the given type */ List<String> getIdAttributeColumnNames(IdentifiableType<?> type) { Attribute idattr = getSingleIdAttribute(type); if (idattr != null) { return getAttributeColumnNames(idattr); } else { List<String> names = new ArrayList<String>(); for (Attribute id: type.getIdClassAttributes()) { names.addAll(getAttributeColumnNames(id)); } return names; } } /** * Get the column names for the given attribute. Recurses into embedded complex types. * @param attr * @return */ List<String> getAttributeColumnNames(Attribute attr) { List<String> names = new ArrayList<String>(); if (attr.getPersistentAttributeType() == PersistentAttributeType.EMBEDDED) { @SuppressWarnings("unchecked") EmbeddableType<?> bed = _emFactory.getMetamodel().embeddable(attr.getJavaType()); for (Attribute<?,?> battr : bed.getAttributes()) { names.addAll(getAttributeColumnNames(battr)); // recursive call } return names; } if (attr.getPersistentAttributeType() == PersistentAttributeType.ONE_TO_MANY || attr.getPersistentAttributeType() == PersistentAttributeType.ONE_TO_ONE) { Class<?> relatedEntityClass = getEntityType(attr); javax.persistence.metamodel.EntityType<?> relatedEntityType = _emFactory.getMetamodel().entity(relatedEntityClass); OneToMany otm = ((AnnotatedElement)attr.getJavaMember()).getAnnotation(OneToMany.class); if (otm != null && !isEmpty(otm.mappedBy())) { Attribute mappedAttr = relatedEntityType.getAttribute(otm.mappedBy()); return getAttributeColumnNames(mappedAttr); } OneToOne oto = ((AnnotatedElement)attr.getJavaMember()).getAnnotation(OneToOne.class); if (oto != null && !isEmpty(oto.mappedBy())) { Attribute mappedAttr = relatedEntityType.getAttribute(oto.mappedBy()); return getAttributeColumnNames(mappedAttr); } return getIdAttributeColumnNames(relatedEntityType); } Column col = ((AnnotatedElement)attr.getJavaMember()).getAnnotation(Column.class); if (col != null && !isEmpty(col.name())) { names.add(col.name()); return names; } else { JoinColumn jcol = ((AnnotatedElement)attr.getJavaMember()).getAnnotation(JoinColumn.class); if (jcol != null && !isEmpty(jcol.name())) { names.add(jcol.name()); return names; } else { JoinColumns jcols = ((AnnotatedElement)attr.getJavaMember()).getAnnotation(JoinColumns.class); if (jcols != null && jcols.value() != null) { for (JoinColumn jjcol : jcols.value()) { names.add(jjcol.name()); } return names; } else { names.add(attr.getName()); return names; } } } } /** * Gets the properties matching the given columns. May be a component, but will not be an association. * @param entityType * @param columnNames * @return */ String[] getPropertyNamesForColumns(javax.persistence.metamodel.EntityType<?> entityType, String[] columnNames) { for (SingularAttribute attr : entityType.getSingularAttributes()) { if (attr.getPersistentAttributeType() == PersistentAttributeType.BASIC || attr.getPersistentAttributeType() == PersistentAttributeType.EMBEDDED) { String[] columnArray = getColumnNames(attr); if (namesEqual(columnArray, columnNames)) return new String[] { attr.getName() }; } } if (columnNames.length > 1) { // go one-by-one through columnNames, trying to find a matching property. // TODO: maybe this should split columnNames into all possible combinations of ordered subsets, and try those ArrayList<String> propList = new ArrayList<String>(); String[] prop = new String[1]; for (int i = 0; i < columnNames.length; i++) { prop[0] = columnNames[i]; String[] names = getPropertyNamesForColumns(entityType, prop); // recursive call if (names != null) propList.addAll(Arrays.asList(names)); } if (propList.size() > 0) return propList.toArray(new String[propList.size()]); } return null; } /** * Unbrackets the column names and concatenates them into a comma-delimited string */ static String catColumnNames(String[] columnNames, char delim) { StringBuilder sb = new StringBuilder(); for (String s : columnNames) { if (sb.length() > 0) sb.append(delim); sb.append(unBracket(s)); } return sb.toString(); //.toLowerCase(); } /** * Return true if the two arrays contain the same names, false otherwise. * Names are compared after unBracket(), and are case-insensitive. * @param a * @param b * @return */ static boolean namesEqual(String[] a, String[] b) { if (a.length != b.length) return false; for (int i = 0; i < a.length; i++) { if (!unBracket(a[i]).equalsIgnoreCase(unBracket(b[i]))) return false; } return true; } /** * Get the column name without square brackets or quotes around it. E.g. "[OrderID]" -> OrderID * Because sometimes Hibernate gives us brackets, and sometimes it doesn't. Double-quotes happen * with SQL CE. Backticks happen with MySQL. */ static String unBracket(String name) { name = (name.charAt(0) == '[') ? name.substring(1, name.length() - 1) : name; name = (name.charAt(0) == '"') ? name.substring(1, name.length() - 1) : name; name = (name.charAt(0) == '`') ? name.substring(1, name.length() - 1) : name; return name; } /** * @return a new array containing the unbracketed names */ static String[] unBracket(String[] names) { String[] u = new String[names.length]; for (int i = 0; i < names.length; i++) { u[i] = unBracket(names[i]); } return u; } static boolean isEmpty(String s) { return s == null || s.length() == 0; } /** * Find the property in the list that has the given name. * @param properties list of DataProperty or NavigationProperty maps * @param name matched against the nameOnServer value of entries in the list * @return the found property map, or null if not found. */ static HashMap<String, Object> findPropertyByName(ArrayList<HashMap<String, Object>> properties, String name) { Object nameOnServer; for (HashMap<String, Object> prop : properties) { nameOnServer = prop.get("nameOnServer"); if (nameOnServer != null) { if (((String) nameOnServer).equalsIgnoreCase(name)) return prop; } } return null; } /** * Get the Breeze name of the entity type. For collections, Breeze expects the name of the * element type. * * @param propType * @return */ Class getEntityType(Attribute attr) { if (attr instanceof Bindable) { return ((Bindable)attr).getBindableJavaType(); } else { throw new RuntimeException("Not Bindable: " + attr); } } /** * Lame pluralizer. Assumes we just need to add a suffix. */ static String pluralize(String s) { if (s == null || s.isEmpty()) return s; int last = s.length() - 1; char c = s.charAt(last); switch (c) { case 'y': return s.substring(0, last) + "ies"; default: return s + 's'; } } /** * Creates an association name from two entity names. For consistency, puts the entity names in * alphabetical order. * * @param name1 * @param name2 * @param columnNames - name of the column(s) on the child entity * @return */ static String getAssociationName(String name1, String name2, String[] columnNames) { String cols = catColumnNames(columnNames, '_'); if (name1.compareTo(name2) < 0) return ASSN + name1 + '_' + name2 + '_' + cols; else return ASSN + name2 + '_' + name1 + '_' + cols; } static final String ASSN = "AN_"; // Map of Hibernate datatype to Breeze datatype. static HashMap<String, String> BreezeTypeMap; // Map of data type to Breeze validation type static HashMap<String, String> ValidationTypeMap; // Set of Breeze types which don't need a maxlength validation static HashSet<String> NoLength; static { BreezeTypeMap = new HashMap<String, String>(); BreezeTypeMap.put("byte[]", "Binary"); BreezeTypeMap.put("binary", "Binary"); BreezeTypeMap.put("binaryblob", "Binary"); BreezeTypeMap.put("blob", "Binary"); BreezeTypeMap.put("timestamp", "DateTime"); BreezeTypeMap.put("timeastimespan", "Time"); BreezeTypeMap.put("short", "Int16"); BreezeTypeMap.put("integer", "Int32"); BreezeTypeMap.put("long", "Int64"); BreezeTypeMap.put("boolean", "Boolean"); BreezeTypeMap.put("byte", "Byte"); BreezeTypeMap.put("datetime", "DateTime"); BreezeTypeMap.put("date", "DateTime"); BreezeTypeMap.put("datetimeoffset", "DateTimeOffset"); BreezeTypeMap.put("big_decimal", "Decimal"); BreezeTypeMap.put("double", "Double"); BreezeTypeMap.put("float", "Single"); BreezeTypeMap.put("uuid", "Guid"); BreezeTypeMap.put("uuid-char", "Guid"); BreezeTypeMap.put("uuid-binary", "Guid"); BreezeTypeMap.put("string", "String"); BreezeTypeMap.put("time", "Time"); NoLength = new HashSet<String>(); NoLength.add("Byte"); NoLength.add("Binary"); NoLength.add("Int16"); NoLength.add("Int32"); NoLength.add("Int64"); NoLength.add("DateTime"); NoLength.add("DateTimeOffset"); NoLength.add("Time"); NoLength.add("Boolean"); NoLength.add("Guid"); NoLength.add("Double"); NoLength.add("Single"); NoLength.add("Decimal"); ValidationTypeMap = new HashMap<String, String>(); ValidationTypeMap.put("Boolean", "bool"); ValidationTypeMap.put("Byte", "byte"); ValidationTypeMap.put("DateTime", "date"); ValidationTypeMap.put("DateTimeOffset", "date"); ValidationTypeMap.put("Decimal", "number"); ValidationTypeMap.put("Double", "number"); ValidationTypeMap.put("Single", "number"); ValidationTypeMap.put("Guid", "guid"); ValidationTypeMap.put("Int16", "int16"); ValidationTypeMap.put("Int32", "int32"); ValidationTypeMap.put("Int64", "int64"); ValidationTypeMap.put("Float", "number"); // ValidationTypeMap.put("String", "string"); ValidationTypeMap.put("Time", "duration"); } }