Auto-detect primary key

I'm doing this by way of a naming convention.
Technically the primary keys for this DB are all Numeric, ending with the String "Numeric".
Cutting this off finds the string key field we want to be using for this application.
This commit is contained in:
michaelpede 2021-05-06 11:19:09 -07:00
parent 87ec0d4642
commit 071732a622
9 changed files with 254 additions and 82 deletions

24
.gitignore vendored
View File

@ -38,3 +38,27 @@ nb-configuration.xml
.externalToolBuilders
maven-eclipse.xml
dependency-reduced-pom.xml
src/main/java/org/reso/service/data/definition/ContactListingNotesDefinition.java
src/main/java/org/reso/service/data/definition/ContactListingsDefinition.java
src/main/java/org/reso/service/data/definition/ContactsDefinition.java
src/main/java/org/reso/service/data/definition/HistoryTransactionalDefinition.java
src/main/java/org/reso/service/data/definition/InternetTrackingDefinition.java
src/main/java/org/reso/service/data/definition/MediaDefinition.java
src/main/java/org/reso/service/data/definition/MemberDefinition.java
src/main/java/org/reso/service/data/definition/OfficeDefinition.java
src/main/java/org/reso/service/data/definition/OpenHouseDefinition.java
src/main/java/org/reso/service/data/definition/OtherPhoneDefinition.java
src/main/java/org/reso/service/data/definition/OUIDDefinition.java
src/main/java/org/reso/service/data/definition/PropertyDefinition.java
src/main/java/org/reso/service/data/definition/PropertyGreenVerificationDefinition.java
src/main/java/org/reso/service/data/definition/PropertyPowerProductionDefinition.java
src/main/java/org/reso/service/data/definition/PropertyRoomsDefinition.java
src/main/java/org/reso/service/data/definition/PropertyUnitTypesDefinition.java
src/main/java/org/reso/service/data/definition/ProspectingDefinition.java
src/main/java/org/reso/service/data/definition/QueueDefinition.java
src/main/java/org/reso/service/data/definition/RulesDefinition.java
src/main/java/org/reso/service/data/definition/SavedSearchDefinition.java
src/main/java/org/reso/service/data/definition/ShowingDefinition.java
src/main/java/org/reso/service/data/definition/SocialMediaDefinition.java
src/main/java/org/reso/service/data/definition/TeamMembersDefinition.java
src/main/java/org/reso/service/data/definition/TeamsDefinition.java

View File

@ -18,9 +18,9 @@ import org.apache.olingo.server.api.serializer.SerializerException;
import org.apache.olingo.server.api.serializer.SerializerResult;
import org.apache.olingo.server.api.uri.*;
import org.apache.olingo.server.api.uri.queryoption.*;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.reso.service.data.common.CommonDataProcessing;
import org.reso.service.data.meta.FieldInfo;
import org.reso.service.data.meta.FilterExpressionVisitor;
import org.reso.service.data.meta.ResourceInfo;
@ -38,22 +38,16 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
private OData odata;
private ServiceMetadata serviceMetadata;
private Connection connect = null;
private ResourceInfo resourceInfo = null;
HashMap<String, ResourceInfo> resourceList = null;
private static final Logger LOG = LoggerFactory.getLogger(GenericEntityCollectionProcessor.class);
public GenericEntityCollectionProcessor(Connection connection, ResourceInfo resourceInfo)
{
this.connect = connection;
this.resourceInfo = resourceInfo;
}
/**
* If you use this constructor, make sure to set your resourceInfo
* @param connection
*/
public GenericEntityCollectionProcessor(Connection connection)
{
this.resourceList = new HashMap<>();
this.connect = connection;
}
@ -62,18 +56,11 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
this.serviceMetadata = serviceMetadata;
}
/**
* Set the attribute with the name that corresponds to this method name.
*
* @param resourceInfo The value to set the attribute to.
*/
protected void setResourceInfo(ResourceInfo resourceInfo)
public void addResource(ResourceInfo resource, String name)
{
this.resourceInfo = resourceInfo;
resourceList.put(name,resource);
}
public void readEntityCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat)
throws ODataApplicationException, SerializerException
{
@ -83,18 +70,23 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0); // in our example, the first segment is the EntitySet
EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet();
String resourceName = uriResourceEntitySet.toString();
ResourceInfo resource = this.resourceList.get(resourceName);
boolean isCount = false;
CountOption countOption = uriInfo.getCountOption();
if (countOption != null) {
isCount = countOption.getValue();
if (isCount){
LOG.info("Count str:"+countOption.getText() );
LOG.debug("Count str:"+countOption.getText() );
}
}
// 2nd: fetch the data from backend for this requested EntitySetName
// it has to be delivered as EntitySet object
EntityCollection entitySet = getData(edmEntitySet, uriInfo, isCount);
EntityCollection entitySet = getData(edmEntitySet, uriInfo, isCount, resource);
// 3rd: create a serializer based on the requested format (json)
try
@ -149,8 +141,8 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString());
}
protected EntityCollection getData(EdmEntitySet edmEntitySet, UriInfo uriInfo, boolean isCount) throws ODataApplicationException {
ArrayList<FieldInfo> fields = this.resourceInfo.getFieldList();
protected EntityCollection getData(EdmEntitySet edmEntitySet, UriInfo uriInfo, boolean isCount, ResourceInfo resource) throws ODataApplicationException {
ArrayList<FieldInfo> fields = resource.getFieldList();
EntityCollection entCollection = new EntityCollection();
@ -159,13 +151,13 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
Map<String, String> properties = System.getenv();
try {
String primaryFieldName = fields.get(0).getFieldName();
String primaryFieldName = resource.getPrimaryKeyName();
FilterOption filter = uriInfo.getFilterOption();
String sqlCriteria = null;
if (filter!=null)
{
sqlCriteria = filter.getExpression().accept(new FilterExpressionVisitor(this.resourceInfo));
sqlCriteria = filter.getExpression().accept(new FilterExpressionVisitor(resource));
}
HashMap<String,Boolean> selectLookup = null;
@ -177,14 +169,14 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
// Logic for $count
if (isCount)
{
queryString = "select count(*) AS rowcount from " + this.resourceInfo.getTableName();
queryString = "select count(*) AS rowcount from " + resource.getTableName();
}
else
{
SelectOption selectOption = uriInfo.getSelectOption();
if (selectOption!=null)
{
selectLookup = new HashMap<String,Boolean>();
selectLookup = new HashMap<>();
selectLookup.put(primaryFieldName,true);
for (SelectItem sel:selectOption.getSelectItems())
@ -197,11 +189,11 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
null, selectOption);
LOG.debug("Select list:"+selectList);
queryString = "select "+selectList+" from " + this.resourceInfo.getTableName();
queryString = "select "+selectList+" from " + resource.getTableName();
}
else
{
queryString = "select * from " + this.resourceInfo.getTableName();
queryString = "select * from " + resource.getTableName();
}
}
if (null!=sqlCriteria && sqlCriteria.length()>0)
@ -285,31 +277,19 @@ public class GenericEntityCollectionProcessor implements EntityCollectionProcess
Object value = null;
if (selectLookup==null || selectLookup.containsKey(fieldName) )
{
if (field.getType().equals(EdmPrimitiveTypeKind.String.getFullQualifiedName()))
{
value = resultSet.getString(fieldName);
}
else
if (field.getType().equals(EdmPrimitiveTypeKind.DateTimeOffset.getFullQualifiedName()))
{
value = resultSet.getTimestamp(fieldName);
}
else
{
LOG.info("Field Name: " + field.getFieldName() + " Field type: " + field.getType());
}
value = CommonDataProcessing.getFieldValueFromRow(field, resultSet);
ent.addProperty(new Property(null, fieldName,ValueType.PRIMITIVE, value));
}
}
ent.setId(createId(this.resourceInfo.getResourcesName(), lookupKey));
ent.setId(createId(resource.getResourcesName(), lookupKey));
productList.add(ent);
}
statement.close();
} catch (Exception e) {
LOG.error("Server Error occurred in reading "+this.resourceInfo.getResourceName(), e);
LOG.error("Server Error occurred in reading "+resource.getResourceName(), e);
return entCollection;
}

View File

@ -22,6 +22,7 @@ import org.apache.olingo.server.api.uri.UriResourceEntitySet;
import org.apache.olingo.server.api.uri.queryoption.FilterOption;
import org.apache.olingo.server.api.uri.queryoption.SkipOption;
import org.apache.olingo.server.api.uri.queryoption.TopOption;
import org.reso.service.data.common.CommonDataProcessing;
import org.reso.service.data.meta.FieldInfo;
import org.reso.service.data.meta.FilterExpressionVisitor;
import org.reso.service.data.meta.ResourceInfo;
@ -34,24 +35,26 @@ import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.*;
public class GenericEntityProcessor implements EntityProcessor
{
private OData odata;
private ServiceMetadata serviceMetadata;
private Connection connect;
ResourceInfo resourceInfo;
HashMap<String, ResourceInfo> resourceList = null;
private static final Logger LOG = LoggerFactory.getLogger(GenericEntityCollectionProcessor.class);
public GenericEntityProcessor(Connection connect, ResourceInfo resourceInfo)
public GenericEntityProcessor(Connection connect)
{
this.connect = connect;
this.resourceInfo = resourceInfo;
this.resourceList = new HashMap<>();
}
public void addResource(ResourceInfo resource, String name)
{
resourceList.put(name,resource);
}
@ -64,9 +67,11 @@ public class GenericEntityProcessor implements EntityProcessor
UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0);
EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet();
ResourceInfo resource = this.resourceList.get(resourcePaths.get(0).toString());
// 2. retrieve the data from backend
List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates();
Entity entity = getData(edmEntitySet, keyPredicates);
Entity entity = getData(edmEntitySet, keyPredicates, resource);
// 3. serialize
EdmEntityType entityType = edmEntitySet.getEntityType();
@ -85,10 +90,8 @@ public class GenericEntityProcessor implements EntityProcessor
response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString());
}
protected Entity getData(EdmEntitySet edmEntitySet, List<UriParameter> keyPredicates) throws ODataApplicationException {
ArrayList<FieldInfo> fields = this.resourceInfo.getFieldList();
EntityCollection entCollection = new EntityCollection();
protected Entity getData(EdmEntitySet edmEntitySet, List<UriParameter> keyPredicates, ResourceInfo resource) throws ODataApplicationException {
ArrayList<FieldInfo> fields = resource.getFieldList();
Entity product = null;
@ -118,7 +121,7 @@ public class GenericEntityProcessor implements EntityProcessor
}
}
queryString = "select * from " + this.resourceInfo.getTableName();
queryString = "select * from " + resource.getTableName();
if (null!=sqlCriteria && sqlCriteria.length()>0)
{
@ -128,7 +131,7 @@ public class GenericEntityProcessor implements EntityProcessor
LOG.debug("SQL Query: "+queryString);
ResultSet resultSet = statement.executeQuery(queryString);
String primaryFieldName = fields.get(0).getFieldName();
String primaryFieldName = resource.getPrimaryKeyName();
// add the lookups from the database.
while (resultSet.next())
@ -139,31 +142,19 @@ public class GenericEntityProcessor implements EntityProcessor
for (FieldInfo field : fields)
{
String fieldName = field.getFieldName();
Object value = null;
if (field.getType().equals(EdmPrimitiveTypeKind.String.getFullQualifiedName()))
{
value = resultSet.getString(fieldName);
}
else if (field.getType().equals(EdmPrimitiveTypeKind.DateTimeOffset.getFullQualifiedName()))
{
value = resultSet.getTimestamp(fieldName);
}
else
{
LOG.info("Field Name: "+field.getFieldName()+" Field type: "+field.getType());
}
Object value = CommonDataProcessing.getFieldValueFromRow(field,resultSet);
ent.addProperty(new Property(null, fieldName, ValueType.PRIMITIVE, value));
}
ent.setId(createId(this.resourceInfo.getResourcesName(), lookupKey));
ent.setId(createId(resource.getResourcesName(), lookupKey));
product = ent;
}
statement.close();
} catch (Exception e) {
LOG.error("Server Error occurred in reading "+this.resourceInfo.getResourceName(), e);
LOG.error("Server Error occurred in reading "+resource.getResourceName(), e);
return product;
}

View File

@ -0,0 +1,37 @@
package org.reso.service.data.common;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.reso.service.data.GenericEntityCollectionProcessor;
import org.reso.service.data.meta.FieldInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
public class CommonDataProcessing
{
private static final Logger LOG = LoggerFactory.getLogger(CommonDataProcessing.class);
public static Object getFieldValueFromRow(FieldInfo field, ResultSet resultSet) throws SQLException
{
String fieldName = field.getFieldName();
Object value = null;
if (field.getType().equals(EdmPrimitiveTypeKind.String.getFullQualifiedName()))
{
value = resultSet.getString(fieldName);
}
else if (field.getType().equals(EdmPrimitiveTypeKind.DateTimeOffset.getFullQualifiedName()))
{
value = resultSet.getTimestamp(fieldName);
}
else
{
LOG.info("Field Name: "+field.getFieldName()+" Field type: "+field.getType());
}
return value;
}
}

View File

@ -35,7 +35,7 @@ public class LookupDefinition extends ResourceInfo
FieldInfo fieldInfo = null;
fieldInfo = new FieldInfo("LookupKey", EdmPrimitiveTypeKind.String.getFullQualifiedName());
fieldInfo.addAnnotation("Lookup Key Field", "RESO.OData.Metadata.StandardName");
fieldInfo.addAnnotation("Lookup Key Field", "RESO.OData.Metadata.DisplayName");
list.add(fieldInfo);
fieldInfo = new FieldInfo("LookupName", EdmPrimitiveTypeKind.String.getFullQualifiedName());

View File

@ -1,8 +1,16 @@
package org.reso.service.data.meta;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.data.ValueType;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.reso.service.data.common.CommonDataProcessing;
import org.reso.service.servlet.RESOservlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.*;
import java.util.ArrayList;
public class ResourceInfo
@ -11,6 +19,9 @@ public class ResourceInfo
protected String resourceName;
protected String resourcesName;
protected FullQualifiedName fqn;
protected String primaryKeyName;
protected static final Logger LOG = LoggerFactory.getLogger(ResourceInfo.class);
/**
* Accessors
@ -31,6 +42,11 @@ public class ResourceInfo
return resourceName;
}
public String getPrimaryKeyName()
{
return primaryKeyName;
}
public ArrayList<FieldInfo> getFieldList()
{
return null;
@ -44,4 +60,25 @@ public class ResourceInfo
return this.fqn;
}
public void findPrimaryKey(Connection connect) throws SQLException
{
String primaryKey = null;
ResultSet pkColumns = connect.getMetaData().getPrimaryKeys(null, null, getTableName());
while(pkColumns.next()) {
String pkColumnName = pkColumns.getString("COLUMN_NAME");
Integer pkPosition = pkColumns.getInt("KEY_SEQ");
LOG.info(""+pkColumnName+" is the "+pkPosition+". column of the primary key of the table "+tableName);
primaryKey = pkColumnName;
}
String[] splitKey = primaryKey.split("Numeric");
if (splitKey.length>=1)
primaryKey = splitKey[0];
this.primaryKeyName = primaryKey;
}
}

View File

@ -1,8 +1,6 @@
package org.reso.service.edmprovider;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.api.edm.provider.*;
import org.apache.olingo.commons.api.ex.ODataException;
import org.reso.service.data.meta.FieldInfo;
import org.reso.service.data.meta.ResourceInfo;
import org.slf4j.Logger;
@ -42,7 +40,7 @@ public class RESOedmProvider extends CsdlAbstractEdmProvider
try
{
ArrayList<FieldInfo> fields = defn.getFieldList();
String primaryFieldName = fields.get(0).getFieldName();
String primaryFieldName = defn.getPrimaryKeyName();
ArrayList<CsdlProperty> propertyList = new ArrayList<>();

View File

@ -14,11 +14,13 @@ import org.reso.service.edmprovider.RESOedmProvider;
import org.reso.service.security.providers.BasicAuthProvider;
import org.reso.service.security.Validator;
import org.reso.service.security.providers.BearerAuthProvider;
import org.reso.service.servlet.util.ClassLoader;
import org.reso.service.servlet.util.SimpleError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
@ -75,19 +77,57 @@ public class RESOservlet extends HttpServlet
this.odata = OData.newInstance();
RESOedmProvider edmProvider = new RESOedmProvider();
ResourceInfo defn = new LookupDefinition();
edmProvider.addDefinition(defn);
ArrayList<ResourceInfo> resources = new ArrayList<>();
// Get all classes with constructors with 0 parameters. LookupDefinition should not work.
try
{
Class[] classList = ClassLoader.getClasses("org.reso.service.data.definition");
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();
resources.add(resource);
resource.findPrimaryKey(this.connect);
}
}
}
catch (Exception e)
{
LOG.error(e.getMessage());
}
// ResourceInfo defn = new LookupDefinition();
ServiceMetadata edm = odata.createServiceMetadata(edmProvider, new ArrayList<EdmxReference>());
// create odata handler and configure it with CsdlEdmProvider and Processor
this.handler = odata.createHandler(edm);
GenericEntityCollectionProcessor lookupEntityCollectionProcessor = new GenericEntityCollectionProcessor(this.connect, defn);
GenericEntityProcessor lookupEntityProcessor = new GenericEntityProcessor(this.connect, defn);
GenericEntityCollectionProcessor entityCollectionProcessor = new GenericEntityCollectionProcessor(this.connect);
GenericEntityProcessor entityProcessor = new GenericEntityProcessor(this.connect);
this.handler.register(lookupEntityCollectionProcessor);
this.handler.register(lookupEntityProcessor);
this.handler.register(entityCollectionProcessor);
this.handler.register(entityProcessor);
for (ResourceInfo resource: resources)
{
LOG.info( "Resource importing: " + resource.getResourceName() );
edmProvider.addDefinition(resource);
entityCollectionProcessor.addResource(resource, resource.getResourceName() );
entityProcessor.addResource(resource, resource.getResourceName() );
}
}

View File

@ -0,0 +1,65 @@
package org.reso.service.servlet.util;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.lang.Class;
public class ClassLoader
{
/**
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
*
* @param packageName The base package
* @return The classes
* @throws ClassNotFoundException
* @throws IOException
*/
public static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException
{
java.lang.ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration resources = classLoader.getResources(path);
List<File> dirs = new ArrayList();
while (resources.hasMoreElements()) {
URL resource = (URL) resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList classes = new ArrayList();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return (Class[]) classes.toArray(new Class[classes.size()]);
}
/**
* Recursive method used to find all classes in a given directory and subdirs.
*
* @param directory The base directory
* @param packageName The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
public static List findClasses(File directory, String packageName) throws ClassNotFoundException {
List classes = new ArrayList();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
}