diff --git a/src/main/java/org/reso/service/data/definition/FieldDefinition.java b/src/main/java/org/reso/service/data/definition/FieldDefinition.java index 519b1d8..9151962 100644 --- a/src/main/java/org/reso/service/data/definition/FieldDefinition.java +++ b/src/main/java/org/reso/service/data/definition/FieldDefinition.java @@ -47,24 +47,29 @@ public class FieldDefinition extends ResourceInfo return FieldDefinition.fieldList; } - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); FieldDefinition.fieldList = list; FieldInfo fieldInfo = null; fieldInfo = new FieldInfo("FieldKey", EdmPrimitiveTypeKind.String.getFullQualifiedName()); fieldInfo.addAnnotation("Field Key Field", "RESO.OData.Metadata.DisplayName"); + fieldInfo.addAnnotation("The key used to uniquely identify the Field.", "Core.Description"); list.add(fieldInfo); fieldInfo = new FieldInfo("ResourceName", EdmPrimitiveTypeKind.String.getFullQualifiedName()); + fieldInfo.addAnnotation("The name of the resource the field belongs to. This will be a RESO Standard Name, when applicable, but may also be a local resource name.", "Core.Description"); list.add(fieldInfo); fieldInfo = new FieldInfo("FieldName", EdmPrimitiveTypeKind.String.getFullQualifiedName()); + fieldInfo.addAnnotation("The name of the field as expressed in the payload. For OData APIs, this field MUST meet certain naming requirements and should be consistent with what’s advertised in the OData XML metadata (to be verified in certification). ", "Core.Description"); list.add(fieldInfo); fieldInfo = new FieldInfo("DisplayName", EdmPrimitiveTypeKind.String.getFullQualifiedName()); + fieldInfo.addAnnotation("The display name for the field. SHOULD be provided in all cases where the use of display names is needed, even if the display name is the same as the underlying field name. The DisplayName MAY be a RESO Standard Display Name or a local one. ", "Core.Description"); list.add(fieldInfo); fieldInfo = new FieldInfo("ModificationTimestamp", EdmPrimitiveTypeKind.DateTimeOffset.getFullQualifiedName()); + fieldInfo.addAnnotation("The timestamp when the field metadata item was last modified. This is used to help rebuild caches when metadata items change so consumers don't have to re-pull and reprocess the entire set of metadata when only a small number of changes have been made.", "Core.Description"); list.add(fieldInfo); return FieldDefinition.fieldList; @@ -74,64 +79,7 @@ public class FieldDefinition extends ResourceInfo public Entity getData(EdmEntitySet edmEntitySet, List keyPredicates) { - ArrayList fields = this.getFieldList(); - - Entity product = null; - - Map properties = System.getenv(); - - try { - - String sqlCriteria = null; - - // Statements allow to issue SQL queries to the database - Statement statement = null; - // Result set get the result of the SQL query - String queryString = null; - - for (final UriParameter key : keyPredicates) - { - // key - String keyName = key.getName(); // .toLowerCase(); - String keyValue = key.getText(); - if (sqlCriteria==null) - { - sqlCriteria = keyName + " = " + keyValue; - } - else - { - sqlCriteria = sqlCriteria + " and " + keyName + " = " + keyValue; - } - } - - queryString = "select * from " + this.getTableName(); - - if (null!=sqlCriteria && sqlCriteria.length()>0) - { - queryString = queryString + " WHERE " + sqlCriteria; - } - - LOG.info("SQL Query: "+queryString); - ResultSet resultSet = statement.executeQuery(queryString); - - String primaryFieldName = this.getPrimaryKeyName(); - - // add the lookups from the database. - while (resultSet.next()) - { - Entity ent = CommonDataProcessing.getEntityFromRow(resultSet, this, null); - - product = ent; - } - - statement.close(); - - } catch (Exception e) { - LOG.error("Server Error occurred in reading "+this.getResourceName(), e); - return product; - } - - return product; + return null; } public EntityCollection getData(EdmEntitySet edmEntitySet, UriInfo uriInfo, boolean isCount) throws ODataApplicationException @@ -173,7 +121,7 @@ public class FieldDefinition extends ResourceInfo for (FieldInfo field: resourceFieldList) { HashMap entityValues = new HashMap<>(); - entityValues.put("FieldKey", field.getFieldName()); + entityValues.put("FieldKey", resourceName.toLowerCase()+'_'+field.getFieldName().toLowerCase() ); entityValues.put("FieldName", field.getFieldName()); entityValues.put("ResourceName", resourceName); entityValues.put("DisplayName", field.getFieldName()); diff --git a/src/main/java/org/reso/service/data/meta/DefinitionBuilder.java b/src/main/java/org/reso/service/data/meta/DefinitionBuilder.java index 119be9b..0dbc609 100644 --- a/src/main/java/org/reso/service/data/meta/DefinitionBuilder.java +++ b/src/main/java/org/reso/service/data/meta/DefinitionBuilder.java @@ -1,6 +1,8 @@ package org.reso.service.data.meta; import com.google.gson.stream.JsonReader; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.FullQualifiedName; import java.io.FileNotFoundException; import java.io.FileReader; @@ -9,38 +11,28 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -class AnnotationObject +class GenericGSONobject { - private static Map FIELD_PROPERTIES = Stream.of( - new AbstractMap.SimpleEntry<>("term", String.class), - new AbstractMap.SimpleEntry<>("value", String.class) ) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); -} + protected static String subArrayName = "annotations"; -class FieldObject -{ - private static Map FIELD_PROPERTIES = Stream.of( - new AbstractMap.SimpleEntry<>("resourceName", String.class), - new AbstractMap.SimpleEntry<>("fieldName", String.class), - new AbstractMap.SimpleEntry<>("type", String.class), - new AbstractMap.SimpleEntry<>("nullable", Boolean.class), - new AbstractMap.SimpleEntry<>("scale", Number.class), - new AbstractMap.SimpleEntry<>("precision", Number.class), - new AbstractMap.SimpleEntry<>("isCollection", Boolean.class), - new AbstractMap.SimpleEntry<>("unicode", Boolean.class) ) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + protected JsonReader reader; + protected HashMap properties = new HashMap<>(); + protected ArrayList subArrayList = new ArrayList<>(); - private JsonReader reader; - private HashMap properties = new HashMap<>(); - - public FieldObject(JsonReader reader) + public GenericGSONobject(JsonReader reader) { this.reader = reader; this.readObject(); } + public Map getPropertiesMeta() + { + return null; + } + private void readObject() { + Map PROPERTIES_META = this.getPropertiesMeta(); try { reader.beginObject(); @@ -49,9 +41,9 @@ class FieldObject String name = reader.nextName(); - if (FIELD_PROPERTIES.containsKey(name)) + if (PROPERTIES_META.containsKey(name)) { - Object classType = FIELD_PROPERTIES.get(name); + Object classType = PROPERTIES_META.get(name); if (classType.equals(String.class)) { @@ -61,17 +53,19 @@ class FieldObject { properties.put(name,reader.nextBoolean() ); } - else if (classType.equals(Number.class)) + else if (classType.equals(Integer.class)) { - properties.put(name,reader.nextLong() ); + properties.put(name,reader.nextInt() ); } - } else if (name.equals("annotations")) { + } else if (name.equals(subArrayName)) { // read array reader.beginArray(); + while (reader.hasNext()) { - reader.skipValue(); - //fields.add( this.readField() ); + GenericGSONobject subArrayItem = this.createSubType(); + + subArrayList.add((SubType)subArrayItem); } reader.endArray(); @@ -88,20 +82,145 @@ class FieldObject } } + public Object getProperty(String name) + { + return properties.get(name); + } + protected GenericGSONobject createSubType() // must also be of type GenericGSONobject + { + return null; + } + + + /** + * Get the annotations for this field. + * @return the sub-array for this generic object + */ + public ArrayList getSubArrayList() + { + return subArrayList; + } + +} + +class FieldObject extends GenericGSONobject +{ + private static Map PROPERTIES_META = Stream.of( + new AbstractMap.SimpleEntry<>("resourceName", String.class), + new AbstractMap.SimpleEntry<>("fieldName", String.class), + new AbstractMap.SimpleEntry<>("type", String.class), + new AbstractMap.SimpleEntry<>("nullable", Boolean.class), + new AbstractMap.SimpleEntry<>("maxLength", Integer.class), + new AbstractMap.SimpleEntry<>("scale", Integer.class), + new AbstractMap.SimpleEntry<>("precision", Integer.class), + new AbstractMap.SimpleEntry<>("isCollection", Boolean.class), + new AbstractMap.SimpleEntry<>("unicode", Boolean.class) ) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + protected static String subArrayName = "annotations"; + + public FieldObject(JsonReader reader) + { + super(reader); + } + + public Map getPropertiesMeta() + { + return PROPERTIES_META; + } + + protected GenericGSONobject createSubType() // must also be of type GenericGSONobject + { + return new AnnotationObject(reader); + } + + /** + * Get the annotations for this field. + * @return + */ + public ArrayList getAnnotations() + { + return getSubArrayList(); + } +} + +class LookupObject extends GenericGSONobject +{ + private static Map PROPERTIES_META = Stream.of( + new AbstractMap.SimpleEntry<>("lookupName", String.class), + new AbstractMap.SimpleEntry<>("lookupValue", String.class), + new AbstractMap.SimpleEntry<>("type", String.class) ) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + protected static String subArrayName = "annotations"; + + public LookupObject(JsonReader reader) + { + super(reader); + } + + public Map getPropertiesMeta() + { + return PROPERTIES_META; + } + + protected GenericGSONobject createSubType() // must also be of type GenericGSONobject + { + return new AnnotationObject(reader); + } + + /** + * Get the annotations for this field. + * @return + */ + public ArrayList getAnnotations() + { + return getSubArrayList(); + } +} + +class AnnotationObject extends GenericGSONobject +{ + private static Map PROPERTIES_META = Stream.of( + new AbstractMap.SimpleEntry<>("term", String.class), + new AbstractMap.SimpleEntry<>("value", String.class) ) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + public AnnotationObject(JsonReader reader) + { + super(reader); + } + + public Map getPropertiesMeta() + { + return PROPERTIES_META; + } } public class DefinitionBuilder { + // Constants + + private static String EDM_ENUM = "org.reso.metadata.enums"; + + private static Map EDM_MAP = Stream.of( + new AbstractMap.SimpleEntry<>("Edm.String", EdmPrimitiveTypeKind.String.getFullQualifiedName() ), + new AbstractMap.SimpleEntry<>("Edm.Boolean", EdmPrimitiveTypeKind.Boolean.getFullQualifiedName() ), + new AbstractMap.SimpleEntry<>("Edm.Decimal", EdmPrimitiveTypeKind.Int64.getFullQualifiedName() )) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + private static Map HEADER_FIELDS = Stream.of( new AbstractMap.SimpleEntry<>("description", true), new AbstractMap.SimpleEntry<>("generatedOn", true), new AbstractMap.SimpleEntry<>("version", true)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + // Internals private String fileName; private JsonReader reader; + // Constructor public DefinitionBuilder(String fileName) { this.fileName = fileName; @@ -122,14 +241,186 @@ public class DefinitionBuilder private FieldObject readField() { - FieldObject fo = new FieldObject(reader); - return fo; + return new FieldObject(reader); + } + + private LookupObject readLookup() + { + return new LookupObject(reader); + } + + private HashMap> createHashFromKey(ArrayList allObjects, String keyName) + { + HashMap> lookup = new HashMap<>(); + + for (GenericGSONobject obj: allObjects) + { + String keyValue = obj.getProperty(keyName).toString(); + ArrayList commonList = lookup.get(keyValue); + if (commonList==null) + { + commonList = new ArrayList<>(); + lookup.put(keyValue, commonList); + } + + commonList.add(obj); + } + + return lookup; + } + + // Function to convert camel case + // string to snake case string + public static String camelToSnake(String str) + { + + // Empty String + String result = ""; + + // Append first character(in lower case) + // to result string + char c = str.charAt(0); + result = result + Character.toLowerCase(c); + + // Traverse the string from + // ist index to last index + for (int i = 1; i < str.length(); i++) { + + char ch = str.charAt(i); + + // Check if the character is upper case + // then append '_' and such character + // (in lower case) to result string + if (Character.isUpperCase(ch)) { + result = result + '_'; + result + = result + + Character.toLowerCase(ch); + } + + // If the character is lower case then + // add such character into result string + else { + result = result + ch; + } + } + + // return the result + return result; + } + + private List createResources(ArrayList fields, ArrayList lookups) + { + HashMap> lookupMap = createHashFromKey(lookups, "lookupName"); + HashMap> fieldMap = createHashFromKey(fields, "resourceName"); + + List resources = new ArrayList<>(); + + for (String resourceName: fieldMap.keySet() ) + { + ArrayList resourceFields = fieldMap.get(resourceName); + + String tableName = resourceName.toLowerCase(); + + if (!"ouid".equals(tableName)) + { + tableName = camelToSnake(resourceName); // @ToDo: This is NOT Guaranteed for all users + } + + ResourceInfo resource = new GenericResourceInfo(resourceName, tableName); + resources.add(resource); + + ArrayList fieldList = resource.getFieldList(); + + for (GenericGSONobject field : resourceFields) + { + + FieldInfo newField = null; + + String fieldName = (String) field.getProperty("fieldName"); + String fieldType = (String) field.getProperty("type"); + Boolean nullable = (Boolean) field.getProperty("nullable"); + Boolean isCollection = (Boolean) field.getProperty("isCollection"); + Integer maxLength = (Integer) field.getProperty("maxLength"); + Integer scale = (Integer) field.getProperty("scale"); + Integer precision = (Integer) field.getProperty("precision"); + + FullQualifiedName fqn = EDM_MAP.get(fieldType); + if (fqn != null) + { + newField = new FieldInfo(fieldName, fqn); + } + else + if (fieldType.startsWith(EDM_ENUM)) + { + String lookupName = fieldType.substring(EDM_ENUM.length()+1 ); + EnumFieldInfo enumFieldInfo = new EnumFieldInfo(fieldName, EdmPrimitiveTypeKind.Int64.getFullQualifiedName()); + enumFieldInfo.setLookupName(lookupName); + newField = enumFieldInfo; + + ArrayList lookupList = lookupMap.get(fieldType); + for (GenericGSONobject lookupItem: lookupList) + { + EnumValueInfo enumValue = new EnumValueInfo((String)lookupItem.getProperty("lookupValue")); + + ArrayList annotations = null; + if (lookupItem.getClass().equals(LookupObject.class)) + { + annotations = ((LookupObject)lookupItem).getAnnotations(); + } + if (annotations!=null) + { + for (AnnotationObject annotation : annotations) + { + enumValue.addAnnotation((String) annotation.getProperty("value"), (String) annotation.getProperty("term")); + } + } + + enumFieldInfo.addValue(enumValue); + } + + } + + if (newField != null) + { + if (maxLength != null) + { + newField.setMaxLength(maxLength); + } + if (scale != null) + { + newField.setScale(scale); + } + if (precision != null) + { + newField.setPrecision(precision); + } + + ArrayList annotations = null; + if (field.getClass().equals(FieldObject.class)) + { + annotations = ((FieldObject)field).getAnnotations(); + } + if (annotations!=null) + { + for (AnnotationObject annotation : annotations) + { + newField.addAnnotation((String) annotation.getProperty("value"), (String) annotation.getProperty("term")); + } + } + fieldList.add(newField); + } + + } + } + + return resources; } public List readResources() { - List resources = new ArrayList<>(); - ArrayList fields = new ArrayList(); + ArrayList fields = new ArrayList(); + ArrayList lookups = new ArrayList(); try { @@ -153,9 +444,19 @@ public class DefinitionBuilder reader.endArray(); + } else if (name.equals("lookups")) { + // read array + reader.beginArray(); + + while (reader.hasNext()) { + lookups.add( this.readLookup() ); + } + + reader.endArray(); } else { reader.skipValue(); //avoid some unhandle events } + } reader.endObject(); @@ -165,6 +466,6 @@ public class DefinitionBuilder e.printStackTrace(); } - return resources; + return createResources(fields, lookups); } } diff --git a/src/main/java/org/reso/service/data/meta/GenericResourceInfo.java b/src/main/java/org/reso/service/data/meta/GenericResourceInfo.java new file mode 100644 index 0000000..30a62be --- /dev/null +++ b/src/main/java/org/reso/service/data/meta/GenericResourceInfo.java @@ -0,0 +1,23 @@ +package org.reso.service.data.meta; + + +import java.util.ArrayList; + +public class GenericResourceInfo extends ResourceInfo +{ + ArrayList fieldList = new ArrayList<>(); + + public GenericResourceInfo(String resourceName, String tableName) + { + this.resourceName = resourceName; + this.resourcesName = resourceName; + this.tableName = tableName; + } + + // Accessor + public ArrayList getFieldList() + { + return fieldList; + } + +} diff --git a/src/main/java/org/reso/service/servlet/RESOservlet.java b/src/main/java/org/reso/service/servlet/RESOservlet.java index 5565c9e..5d6a472 100644 --- a/src/main/java/org/reso/service/servlet/RESOservlet.java +++ b/src/main/java/org/reso/service/servlet/RESOservlet.java @@ -117,48 +117,63 @@ public class RESOservlet extends HttpServlet // If there is a Certification metadata report file, import it for class definitions. - if (definitionFile!=null && false) + if (definitionFile!=null) { DefinitionBuilder definitionBuilder = new DefinitionBuilder(definitionFile); List loadedResources = definitionBuilder.readResources(); - } - - // Get all classes with constructors with 0 parameters. LookupDefinition should not work. - try - { - Class[] classList = ClassLoader.getClasses("org.reso.service.data.definition.custom"); - for (Class classProto: classList) + for (ResourceInfo resource: loadedResources) { - Constructor ctor = null; - Constructor[] ctors = classProto.getDeclaredConstructors(); - for (int i = 0; i < ctors.length; i++) { - ctor = ctors[i]; - if (ctor.getGenericParameterTypes().length == 0) - break; - } - if (ctor!=null) + try { - ctor.setAccessible(true); - ResourceInfo resource = (ResourceInfo)ctor.newInstance(); - - try - { - resource.findPrimaryKey(this.connect); - resources.add(resource); - resourceLookup.put(resource.getResourceName(), resource); - } - catch (Exception e) - { - LOG.error("Error with: "+resource.getResourceName()+" - "+e.getMessage()); - } + resource.findPrimaryKey(this.connect); + resources.add(resource); + } + catch (SQLException e) + { + LOG.error("Error with: "+resource.getResourceName()+" - "+e.getMessage()); } } } - catch (Exception e) + else { - LOG.error(e.getMessage()); + // Get all classes with constructors with 0 parameters. LookupDefinition should not work. + try + { + Class[] classList = ClassLoader.getClasses("org.reso.service.data.definition.custom"); + for (Class classProto: classList) + { + Constructor ctor = null; + Constructor[] ctors = classProto.getDeclaredConstructors(); + for (int i = 0; i < ctors.length; i++) { + ctor = ctors[i]; + if (ctor.getGenericParameterTypes().length == 0) + break; + } + if (ctor!=null) + { + ctor.setAccessible(true); + ResourceInfo resource = (ResourceInfo)ctor.newInstance(); + + try + { + resource.findPrimaryKey(this.connect); + resources.add(resource); + resourceLookup.put(resource.getResourceName(), resource); + } + catch (Exception e) + { + LOG.error("Error with: "+resource.getResourceName()+" - "+e.getMessage()); + } + } + } + } + catch (Exception e) + { + LOG.error(e.getMessage()); + } } + ResourceInfo defn = new LookupDefinition(); try {