Skip to content

Commit

Permalink
TEIID-5559: odata4 NPE when accessing VDB including FK relationship a…
Browse files Browse the repository at this point in the history
…cross models (allowing fks to cross schemas)
  • Loading branch information
shawkins authored and johnathonlee committed Jan 15, 2019
1 parent 41a6d66 commit a9d16e6
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 65 deletions.
Expand Up @@ -266,6 +266,9 @@ <h4 class="western">from ${project.version}</h4>
<li/>
<p style="margin-bottom: 0in">
<a href='https://issues.jboss.org/browse/TEIID-5550'>TEIID-5550</a> - DISTINCT pushed down incorrectly in some cases (fix for inappropriate dup node removal over join)
<li/>
<p style="margin-bottom: 0in">
<a href='https://issues.jboss.org/browse/TEIID-5559'>TEIID-5559</a> - odata4 NPE when accessing VDB including FK relationship across models (allowing fks to cross schemas)
<li/>
<p style="margin-bottom: 0in">
<a href='https://issues.jboss.org/browse/TEIID-5587'>TEIID-5587</a> - Oracle 11 drivers don't accept NVARCHAR type (refining to allow latin 1 supplement as ascii)
Expand Down
127 changes: 88 additions & 39 deletions olingo/src/main/java/org/teiid/olingo/service/ODataSchemaBuilder.java
Expand Up @@ -47,13 +47,43 @@
import org.teiid.olingo.common.ODataTypeManager;

public class ODataSchemaBuilder {

public interface SchemaResolver {
/**
* Return the schema info or null if it is not visible
* @param schemaName
* @return
*/
ODataSchemaInfo getSchemaInfo(String schemaName);
}

public final static class ODataSchemaInfo {
public CsdlSchema schema = new CsdlSchema();
public Map<String, CsdlEntitySet> entitySets = new LinkedHashMap<String, CsdlEntitySet>();
public Map<String, CsdlEntityType> entityTypes = new LinkedHashMap<String, CsdlEntityType>();
public TeiidEdmProvider edmProvider;
}

/**
* Helper method for tests
* @param namespace
* @param teiidSchema
* @return
*/
public static CsdlSchema buildMetadata(String namespace, org.teiid.metadata.Schema teiidSchema) {
ODataSchemaInfo info = buildStructuralMetadata(namespace, teiidSchema);
buildNavigationProperties(teiidSchema, info.entityTypes, info.entitySets, null);
return info.schema;
}

public static ODataSchemaInfo buildStructuralMetadata(String namespace, org.teiid.metadata.Schema teiidSchema) {
try {
CsdlSchema edmSchema = new CsdlSchema();
buildEntityTypes(namespace, teiidSchema, edmSchema);
buildProcedures(teiidSchema, edmSchema);
return edmSchema;
ODataSchemaInfo info = new ODataSchemaInfo();
String fullSchemaName = namespace+"."+teiidSchema.getName();
info.schema.setNamespace(fullSchemaName).setAlias(teiidSchema.getName());
buildEntityTypes(namespace, teiidSchema, info.schema, info.entitySets, info.entityTypes);
buildProcedures(teiidSchema, info.schema);
return info;
} catch (Exception e) {
throw new TeiidRuntimeException(e);
}
Expand Down Expand Up @@ -91,10 +121,8 @@ static CsdlEntityContainer findEntityContainer(Map<String, CsdlSchema> edmSchema
return schema.getEntityContainer();
}

static void buildEntityTypes(String namespace, org.teiid.metadata.Schema schema, CsdlSchema edmSchema) {
Map<String, CsdlEntitySet> entitySets = new LinkedHashMap<String, CsdlEntitySet>();
Map<String, CsdlEntityType> entityTypes = new LinkedHashMap<String, CsdlEntityType>();

static void buildEntityTypes(String namespace, org.teiid.metadata.Schema schema, CsdlSchema csdlSchema, Map<String,
CsdlEntitySet> entitySets, Map<String, CsdlEntityType> entityTypes) {
String fullSchemaName = namespace+"."+schema.getName();

for (Table table : schema.getTables().values()) {
Expand Down Expand Up @@ -140,18 +168,14 @@ static void buildEntityTypes(String namespace, org.teiid.metadata.Schema schema,
entitySets.put(entityTypeName, entitySet);
}


buildNavigationProperties(schema, entityTypes, entitySets);


// entity container is holder entity sets, association sets, function
// imports
CsdlEntityContainer entityContainer = new CsdlEntityContainer().setName(
schema.getName()).setEntitySets(
new ArrayList<CsdlEntitySet>(entitySets.values()));

// build entity schema
edmSchema.setNamespace(fullSchemaName).setAlias(schema.getName())
csdlSchema.setNamespace(fullSchemaName).setAlias(schema.getName())
.setEntityTypes(new ArrayList<CsdlEntityType>(entityTypes.values()))
.setEntityContainer(entityContainer);
}
Expand Down Expand Up @@ -231,8 +255,8 @@ static KeyRecord getIdentifier(Table table) {
return null;
}

private static void buildNavigationProperties(org.teiid.metadata.Schema schema,
Map<String, CsdlEntityType> entityTypes, Map<String, CsdlEntitySet> entitySets) {
static void buildNavigationProperties(org.teiid.metadata.Schema schema,
Map<String, CsdlEntityType> entityTypes, Map<String, CsdlEntitySet> entitySets, SchemaResolver resolver) {

for (Table table : schema.getTables().values()) {

Expand All @@ -247,19 +271,20 @@ private static void buildNavigationProperties(org.teiid.metadata.Schema schema,
// check to see if fk is part of this table's pk, then it is 1 to 1 relation
boolean fkPKSame = sameColumnSet(getIdentifier(table), fk);

addForwardNavigation(entityTypes, entitySets, table, fk, fkPKSame);
addReverseNavigation(entityTypes, entitySets, table, fk, fkPKSame);
addForwardNavigation(entityTypes, entitySets, table, fk, fkPKSame, resolver);
addReverseNavigation(entityTypes, entitySets, table, fk, fkPKSame, resolver);
}
}
}

private static void addForwardNavigation(Map<String, CsdlEntityType> entityTypes,
Map<String, CsdlEntitySet> entitySets, Table table, ForeignKey fk, boolean onetoone) {
CsdlNavigationProperty navigaton = null;
CsdlNavigationPropertyBinding navigationBinding = null;
Map<String, CsdlEntitySet> entitySets, Table table, ForeignKey fk, boolean onetoone, SchemaResolver resolver) {
CsdlNavigationPropertyBinding navigationBinding = buildNavigationBinding(fk, resolver);
if (navigationBinding == null) {
return;
}
CsdlNavigationProperty navigaton = buildNavigation(fk);
String entityTypeName = table.getName();
navigaton = buildNavigation(fk);
navigationBinding = buildNavigationBinding(fk);

if (onetoone) {
navigaton.setNullable(false);
Expand Down Expand Up @@ -288,40 +313,64 @@ private static void addForwardNavigation(Map<String, CsdlEntityType> entityTypes
}

private static void addReverseNavigation(Map<String, CsdlEntityType> entityTypes,
Map<String, CsdlEntitySet> entitySets, Table table, ForeignKey fk, boolean onetoone) {
CsdlNavigationProperty navigaton = null;
CsdlNavigationPropertyBinding navigationBinding = null;
String entityTypeName = null;

entityTypeName = fk.getReferenceTableName();
navigaton = buildReverseNavigation(table, fk);
navigationBinding = buildReverseNavigationBinding(table,fk);
Map<String, CsdlEntitySet> entitySets, Table table, ForeignKey fk, boolean onetoone, SchemaResolver resolver) {
CsdlNavigationPropertyBinding navigationBinding = buildReverseNavigationBinding(table,fk, resolver);
if (navigationBinding == null) {
return;
}
CsdlNavigationProperty navigaton = buildReverseNavigation(table, fk);
String entityTypeName = fk.getReferenceTableName();
String entitySchema = fk.getReferenceKey().getParent().getParent().getName();

if (onetoone) {
navigaton.setNullable(false);
} else {
navigaton.setCollection(true);
}


CsdlEntityType entityType = entityTypes.get(entityTypeName);
CsdlEntityType entityType = null;
CsdlEntitySet entitySet = null;
if (entitySchema.equals(table.getParent().getName())) {
entityType = entityTypes.get(entityTypeName);
entitySet = entitySets.get(entityTypeName);
} else {
ODataSchemaInfo schema = resolver.getSchemaInfo(entitySchema);
if (schema == null) {
return;
}
entityType = schema.entityTypes.get(entityTypeName);
entitySet = schema.entitySets.get(entityTypeName);
}
entityType.getNavigationProperties().add(navigaton);

CsdlEntitySet entitySet = entitySets.get(entityTypeName);
entitySet.getNavigationPropertyBindings().add(navigationBinding);
}

private static CsdlNavigationPropertyBinding buildNavigationBinding(ForeignKey fk) {
private static CsdlNavigationPropertyBinding buildNavigationBinding(ForeignKey fk, SchemaResolver resolver) {
CsdlNavigationPropertyBinding navigationBinding = new CsdlNavigationPropertyBinding();
navigationBinding.setPath(fk.getName());
navigationBinding.setTarget(fk.getReferenceTableName());
if (!fk.getParent().getParent().equals(fk.getReferenceKey().getParent().getParent())) {
ODataSchemaInfo schema = resolver.getSchemaInfo(fk.getReferenceKey().getParent().getParent().getName());
if (schema == null) {
return null;
}
navigationBinding.setTarget(fk.getReferenceKey().getParent().getFullName());
} else {
navigationBinding.setTarget(fk.getReferenceKey().getParent().getName());
}
return navigationBinding;
}

private static CsdlNavigationPropertyBinding buildReverseNavigationBinding(Table table, ForeignKey fk) {
private static CsdlNavigationPropertyBinding buildReverseNavigationBinding(Table table, ForeignKey fk, SchemaResolver resolver) {
CsdlNavigationPropertyBinding navigationBinding = new CsdlNavigationPropertyBinding();
navigationBinding.setPath(table.getName()+"_"+fk.getName());
navigationBinding.setTarget(table.getName());
if (!table.getParent().equals(fk.getReferenceKey().getParent().getParent())) {
ODataSchemaInfo schema = resolver.getSchemaInfo(fk.getReferenceKey().getParent().getParent().getName());
if (schema == null) {
return null;
}
navigationBinding.setTarget(table.getFullName());
} else {
navigationBinding.setTarget(table.getName());
}
return navigationBinding;
}

Expand Down
95 changes: 74 additions & 21 deletions olingo/src/main/java/org/teiid/olingo/service/OlingoBridge.java
Expand Up @@ -21,51 +21,104 @@
*/
package org.teiid.olingo.service;

import java.util.concurrent.ConcurrentHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;

import javax.servlet.ServletException;
import javax.xml.stream.XMLStreamException;

import org.apache.olingo.commons.api.edm.provider.CsdlSchema;
import org.apache.olingo.commons.api.ex.ODataException;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataHttpHandler;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.core.OData4Impl;
import org.teiid.adminapi.Model;
import org.teiid.adminapi.impl.VDBMetaData;
import org.teiid.metadata.Schema;
import org.teiid.odata.api.Client;
import org.teiid.olingo.ODataPlugin;
import org.teiid.olingo.service.ODataSchemaBuilder.ODataSchemaInfo;
import org.teiid.olingo.service.ODataSchemaBuilder.SchemaResolver;

public class OlingoBridge {

private ConcurrentHashMap<String, ODataHttpHandler> handlers = new ConcurrentHashMap<String, ODataHttpHandler>();
//the schema name is handled as case insensitive
private ConcurrentSkipListMap<String, ODataHttpHandler> handlers = new ConcurrentSkipListMap<String, ODataHttpHandler>(String.CASE_INSENSITIVE_ORDER);

public ODataHttpHandler getHandler(String baseUri, Client client, String schemaName) throws ServletException {
if (this.handlers.get(schemaName) == null) {
org.teiid.metadata.Schema teiidSchema = client.getMetadataStore().getSchema(schemaName);
if (teiidSchema == null || !isVisible(client.getVDB(), teiidSchema)) {
throw new ServletException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16022));
ODataHttpHandler handler = this.handlers.get(schemaName);
if (handler != null) {
return handler;
}
VDBMetaData vdb = client.getVDB();

org.teiid.metadata.Schema teiidSchema = client.getMetadataStore().getSchema(schemaName);
if (teiidSchema == null || !isVisible(vdb, teiidSchema)) {
throw new ServletException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16022));
}

synchronized (this) {
handler = this.handlers.get(schemaName);
if (handler != null) {
return handler;
}


loadAllHandlers(baseUri, client, vdb);
}
return handlers.get(schemaName);
}

private void loadAllHandlers(String baseUri, Client client, final VDBMetaData vdb)
throws ServletException {
final Map<String, ODataSchemaInfo> infoMap = new LinkedHashMap<String, ODataSchemaInfo>();

//process the base metadata structure
for (Schema s : client.getMetadataStore().getSchemaList()) {
if (!isVisible(vdb, s)) {
continue;
}
ODataSchemaInfo info = ODataSchemaBuilder.buildStructuralMetadata(vdb.getFullName(), s);
infoMap.put(s.getName(), info);
try {
OData odata = OData4Impl.newInstance();
VDBMetaData vdb = client.getVDB();
CsdlSchema schema = ODataSchemaBuilder.buildMetadata(vdb.getFullName(), teiidSchema);
TeiidEdmProvider edmProvider = new TeiidEdmProvider(baseUri, schema,
info.edmProvider = new TeiidEdmProvider(baseUri, info.schema,
client.getProperty(Client.INVALID_CHARACTER_REPLACEMENT));
ServiceMetadata metadata = odata.createServiceMetadata(edmProvider, edmProvider.getReferences());
ODataHttpHandler handler = odata.createHandler(metadata);

handler.register(new TeiidServiceHandler(schemaName));
this.handlers.put(schemaName, handler);
} catch (XMLStreamException e) {
throw new ServletException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16054));
} catch (ODataException e) {
throw new ServletException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16054));
}
}
return this.handlers.get(schemaName);

//process navigation links
for (Schema s : client.getMetadataStore().getSchemaList()) {
if (!isVisible(vdb, s)) {
continue;
}
final ODataSchemaInfo info = infoMap.get(s.getName());
ODataSchemaBuilder.buildNavigationProperties(s, info.entityTypes, info.entitySets, new SchemaResolver() {

@Override
public ODataSchemaInfo getSchemaInfo(String name) {
ODataSchemaInfo result = infoMap.get(name);
if (result != null) {
//add a bi-directional relationship
info.edmProvider.addReferenceSchema(
vdb.getFullName(), result.schema.getNamespace(), result.schema.getAlias(), result.edmProvider);
result.edmProvider.addReferenceSchema(
vdb.getFullName(), info.schema.getNamespace(), info.schema.getAlias(), info.edmProvider);
}
return result;
}
});
}

OData odata = OData4Impl.newInstance();

for (Map.Entry<String, ODataSchemaInfo> entry : infoMap.entrySet()) {
TeiidEdmProvider edmProvider = entry.getValue().edmProvider;
ServiceMetadata metadata = odata.createServiceMetadata(edmProvider, edmProvider.getReferences());
ODataHttpHandler handler = odata.createHandler(metadata);
handler.register(new TeiidServiceHandler(entry.getKey()));
this.handlers.put(entry.getKey(), handler);
}
}

private static boolean isVisible(VDBMetaData vdb, org.teiid.metadata.Schema schema) {
Expand Down
Expand Up @@ -37,9 +37,13 @@
import org.apache.olingo.server.core.SchemaBasedEdmProvider;

public class TeiidEdmProvider extends SchemaBasedEdmProvider {

private String baseUri;

public TeiidEdmProvider(String baseUri, CsdlSchema schema,
String invalidXmlReplacementChar) throws XMLStreamException,
ODataException {
String invalidXmlReplacementChar) throws XMLStreamException {

this.baseUri = baseUri;

EdmxReference olingoRef = new EdmxReference(URI.create(baseUri+"/static/org.apache.olingo.v1.xml"));
EdmxReferenceInclude include = new EdmxReferenceInclude("org.apache.olingo.v1", "olingo-extensions");
Expand All @@ -64,5 +68,13 @@ public TeiidEdmProvider(String baseUri, CsdlSchema schema,
}
addSchema(schema);
}

public void addReferenceSchema(String vdbName, String ns, String alias, SchemaBasedEdmProvider provider) {
super.addReferenceSchema(ns, provider);
String uri = baseUri + "/" + vdbName + "/" + alias + "/$metadata";
super.addReference(new EdmxReference(URI.create(uri))
.addInclude(new EdmxReferenceInclude(ns, alias)));
}

}

0 comments on commit a9d16e6

Please sign in to comment.