/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.translator.odata;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import org.core4j.Enumerable;
import org.odata4j.edm.*;
import org.odata4j.edm.EdmFunctionParameter.Mode;
import org.odata4j.edm.EdmProperty.CollectionKind;
import org.odata4j.edm.EdmSchema.Builder;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.metadata.*;
public class ODataEntitySchemaBuilder {
static EdmDataServices buildMetadata(MetadataStore metadataStore) {
try {
List<EdmSchema.Builder> edmSchemas = new ArrayList<EdmSchema.Builder>();
for (Schema schema:metadataStore.getSchemaList()) {
buildEntityTypes(schema, edmSchemas, true);
buildFunctionImports(schema, edmSchemas, true);
buildAssosiationSets(schema, edmSchemas, true);
}
return EdmDataServices.newBuilder().addSchemas(edmSchemas).build();
} catch (Exception e) {
throw new TeiidRuntimeException(e);
}
}
static EdmDataServices buildMetadata(Schema schema) {
try {
List<EdmSchema.Builder> edmSchemas = new ArrayList<EdmSchema.Builder>();
buildEntityTypes(schema, edmSchemas, true);
buildFunctionImports(schema, edmSchemas, true);
buildAssosiationSets(schema, edmSchemas, true);
return EdmDataServices.newBuilder().addSchemas(edmSchemas).build();
} catch (Exception e) {
throw new TeiidRuntimeException(e);
}
}
static org.odata4j.edm.EdmEntitySet.Builder findEntitySet(List<Builder> edmSchemas, String schemaName, String enitityName) {
for (EdmSchema.Builder modelSchema:edmSchemas) {
if (modelSchema.getNamespace().equals(schemaName)) {
for (EdmEntityContainer.Builder entityContainter:modelSchema.getEntityContainers()) {
for(EdmEntitySet.Builder entitySet:entityContainter.getEntitySets()) {
if (entitySet.getName().equals(enitityName)) {
return entitySet;
}
}
}
}
}
return null;
}
static EdmSchema.Builder findSchema(List<Builder> edmSchemas, String schemaName) {
for (EdmSchema.Builder modelSchema:edmSchemas) {
if (modelSchema.getNamespace().equals(schemaName)) {
return modelSchema;
}
}
return null;
}
static org.odata4j.edm.EdmEntityType.Builder findEntityType(List<Builder> edmSchemas, String schemaName, String enitityName) {
for (EdmSchema.Builder modelSchema:edmSchemas) {
if (modelSchema.getNamespace().equals(schemaName)) {
for (EdmEntityType.Builder type:modelSchema.getEntityTypes()) {
if (type.getName().equals(enitityName)) {
return type;
}
}
}
}
return null;
}
static EdmEntityContainer.Builder findEntityContainer(List<Builder> edmSchemas, String schemaName) {
for (EdmSchema.Builder modelSchema:edmSchemas) {
if (modelSchema.getNamespace().equals(schemaName)) {
for (EdmEntityContainer.Builder entityContainter:modelSchema.getEntityContainers()) {
return entityContainter;
}
}
}
return null;
}
public static void buildEntityTypes(Schema schema, List<EdmSchema.Builder> edmSchemas, boolean preserveEntityTypeName) {
List<EdmEntitySet.Builder> entitySets = new ArrayList<EdmEntitySet.Builder>();
List<EdmEntityType.Builder> entityTypes = new ArrayList<EdmEntityType.Builder>();
LinkedHashMap<String, EdmComplexType.Builder> complexTypes = new LinkedHashMap<String, EdmComplexType.Builder>();
if (preserveEntityTypeName) {
//first pass, build complex types
for (Table table: schema.getTables().values()) {
// skip if the table does not have the PK or unique
KeyRecord primaryKey = table.getPrimaryKey();
List<KeyRecord> uniques = table.getUniqueKeys();
if (primaryKey == null && uniques.isEmpty()) {
LogManager.logDetail(LogConstants.CTX_ODATA, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17017, table.getFullName()));
continue;
}
for (Column c : table.getColumns()) {
String columnGroup = c.getProperty(ODataMetadataProcessor.COLUMN_GROUP, false);
String name = c.getName();
if (columnGroup != null) {
//use the real name not the group_column name
name = c.getSourceName();
}
String complexType = c.getProperty(ODataMetadataProcessor.COMPLEX_TYPE, false);
if (complexType == null) {
continue;
}
EdmComplexType.Builder complexTypeBuilder = complexTypes.get(complexType);
if (complexTypeBuilder == null) {
complexTypeBuilder = EdmComplexType.newBuilder();
complexTypes.put(complexType, complexTypeBuilder);
complexTypeBuilder.setName(complexType);
complexTypeBuilder.setNamespace(schema.getName());
} else if (complexTypeBuilder.findProperty(name) != null) {
continue; //already added
}
EdmProperty.Builder property = EdmProperty.newBuilder(name)
.setType(ODataTypeManager.odataType(c.getRuntimeType()))
.setNullable(isPartOfPrimaryKey(table, c.getName())?false:c.getNullType() == NullType.Nullable);
if (c.getRuntimeType().equals(DataTypeManager.DefaultDataTypes.STRING)) {
property.setFixedLength(c.isFixedLength())
.setMaxLength(c.getLength())
.setUnicode(true);
}
complexTypeBuilder.addProperties(property);
}
}
}
//second pass, add all columns
for (Table table: schema.getTables().values()) {
// skip if the table does not have the PK or unique
KeyRecord primaryKey = table.getPrimaryKey();
List<KeyRecord> uniques = table.getUniqueKeys();
if (primaryKey == null && uniques.isEmpty()) {
LogManager.logDetail(LogConstants.CTX_ODATA, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17017, table.getFullName()));
continue;
}
String entityTypeName = table.getName();
if (preserveEntityTypeName && table.getProperty(ODataMetadataProcessor.ENTITY_TYPE, false) != null) {
entityTypeName = table.getProperty(ODataMetadataProcessor.ENTITY_TYPE, false);
}
EdmEntityType.Builder entityType = EdmEntityType
.newBuilder().setName(entityTypeName)
.setNamespace(schema.getName());
// adding key
if (primaryKey != null) {
for (Column c : primaryKey.getColumns()) {
entityType.addKeys(c.getName());
}
}
else {
for (Column c : uniques.get(0).getColumns()) {
entityType.addKeys(c.getName());
}
}
HashSet<String> columnGroups = new HashSet<String>();
// adding properties
for (Column c : table.getColumns()) {
String complexType = c.getProperty(ODataMetadataProcessor.COMPLEX_TYPE, false);
if (complexType != null && preserveEntityTypeName) {
String columnGroup = c.getProperty(ODataMetadataProcessor.COLUMN_GROUP, false);
if (!columnGroups.add(columnGroup)) {
continue;
}
EdmProperty.Builder property = EdmProperty.newBuilder(columnGroup)
.setType(complexTypes.get(complexType).build());
entityType.addProperties(property);
continue;
}
EdmProperty.Builder property = EdmProperty.newBuilder(c.getName())
.setType(ODataTypeManager.odataType(c.getRuntimeType()))
.setNullable(isPartOfPrimaryKey(table, c.getName())?false:c.getNullType() == NullType.Nullable);
if (c.getRuntimeType().equals(DataTypeManager.DefaultDataTypes.STRING)) {
property.setFixedLength(c.isFixedLength())
.setMaxLength(c.getLength())
.setUnicode(true);
}
entityType.addProperties(property);
}
// entity set one for one entity type
EdmEntitySet.Builder entitySet = EdmEntitySet.newBuilder()
.setName(table.getName())
.setEntityType(entityType);
entityType.setNamespace(schema.getName());
entitySets.add(entitySet);
// add enitity types for entity schema
entityTypes.add(entityType);
}
// entity container is holder entity sets, association sets, function imports
EdmEntityContainer.Builder entityContainer = EdmEntityContainer
.newBuilder().setName(schema.getName())
.setIsDefault(false)
.addEntitySets(entitySets);
// build entity schema
EdmSchema.Builder modelSchema = EdmSchema.newBuilder()
.setNamespace(schema.getName())
.addEntityTypes(entityTypes)
.addEntityContainers(entityContainer)
.addComplexTypes(complexTypes.values());
edmSchemas.add(modelSchema);
}
static boolean isPartOfPrimaryKey(Table table, String columnName) {
KeyRecord pk = table.getPrimaryKey();
if (hasColumn(pk, columnName)) {
return true;
}
for (KeyRecord key:table.getUniqueKeys()) {
if (hasColumn(key, columnName)) {
return true;
}
}
return false;
}
static boolean hasColumn(KeyRecord pk, String columnName) {
if (pk != null) {
return pk.getColumnByName(columnName) != null;
}
return false;
}
private static String getEntityTypeName(Table t, boolean preserveEntityTypeName) {
String entityTypeName = t.getName();
if (preserveEntityTypeName && t.getProperty(ODataMetadataProcessor.ENTITY_TYPE, false) != null) {
entityTypeName = t.getProperty(ODataMetadataProcessor.ENTITY_TYPE, false);
}
return entityTypeName;
}
public static void buildAssosiationSets(Schema schema, List<Builder> edmSchemas, boolean preserveEntityTypeName) {
EdmSchema.Builder odataSchema = findSchema(edmSchemas, schema.getName());
EdmEntityContainer.Builder entityContainer = findEntityContainer(edmSchemas, schema.getName());
List<EdmAssociationSet.Builder> assosiationSets = new ArrayList<EdmAssociationSet.Builder>();
List<EdmAssociation.Builder> assosiations = new ArrayList<EdmAssociation.Builder>();
for (Table table: schema.getTables().values()) {
// skip if the table does not have the PK or unique
KeyRecord primaryKey = table.getPrimaryKey();
List<KeyRecord> uniques = table.getUniqueKeys();
if (primaryKey == null && uniques.isEmpty()) {
continue;
}
// build Associations
for (ForeignKey fk:table.getForeignKeys()) {
EdmEntitySet.Builder entitySet = findEntitySet(edmSchemas,schema.getName(), table.getName());
EdmEntitySet.Builder refEntitySet = findEntitySet(edmSchemas, schema.getName(),fk.getReferenceTableName());
EdmEntityType.Builder entityType = findEntityType(edmSchemas, schema.getName(), getEntityTypeName(table, preserveEntityTypeName));
EdmEntityType.Builder refEntityType = findEntityType(edmSchemas, schema.getName(), getEntityTypeName(fk.getPrimaryKey().getParent(), preserveEntityTypeName));
// check to see if fk is part of this table's pk, then it is 1 to 1 relation
boolean onetoone = sameColumnSet(table.getPrimaryKey(), fk);
// Build Association Ends
EdmAssociationEnd.Builder endSelf = EdmAssociationEnd.newBuilder()
.setRole(table.getName())
.setType(entityType)
.setMultiplicity(onetoone?EdmMultiplicity.ZERO_TO_ONE:EdmMultiplicity.MANY);
EdmAssociationEnd.Builder endRef = EdmAssociationEnd.newBuilder()
.setRole(fk.getReferenceTableName())
.setType(refEntityType)
.setMultiplicity(EdmMultiplicity.ZERO_TO_ONE);
// Build Association
EdmAssociation.Builder association = EdmAssociation.newBuilder();
association.setName(table.getName()+"_"+fk.getName()); //$NON-NLS-1$
association.setEnds(endSelf, endRef);
association.setNamespace(refEntityType.getFullyQualifiedTypeName().substring(0, refEntityType.getFullyQualifiedTypeName().indexOf('.')));
assosiations.add(association);
// Build ReferentialConstraint
if (fk.getReferenceColumns() != null) {
EdmReferentialConstraint.Builder erc = EdmReferentialConstraint.newBuilder();
erc.setPrincipalRole(fk.getReferenceTableName());
erc.addPrincipalReferences(fk.getReferenceColumns());
erc.setDependentRole(table.getName());
erc.addDependentReferences(getColumnNames(fk.getColumns()));
association.setRefConstraint(erc);
}
if (!fk.getReferenceTableName().equalsIgnoreCase(table.getName())) {
// Add EdmNavigationProperty to entity type
String navigationName = getNavigationName(entityType, fk.getReferenceTableName());
EdmNavigationProperty.Builder nav = EdmNavigationProperty.newBuilder(navigationName);
nav.setRelationshipName(fk.getName());
nav.setFromToName(table.getName(), fk.getReferenceTableName());
nav.setRelationship(association);
nav.setFromTo(endSelf, endRef);
entityType.addNavigationProperties(nav);
}
// Add EdmNavigationProperty to Reference entity type
String navigationName = getNavigationName(refEntityType, table.getName());
EdmNavigationProperty.Builder refNav = EdmNavigationProperty.newBuilder(navigationName);
refNav.setRelationshipName(fk.getName());
refNav.setFromToName(fk.getReferenceTableName(), table.getName());
refNav.setRelationship(association);
refNav.setFromTo(endRef, endSelf);
refEntityType.addNavigationProperties(refNav);
// build AssosiationSet
EdmAssociationSet.Builder assosiationSet = EdmAssociationSet.newBuilder()
.setName(table.getName()+"_"+fk.getName()) //$NON-NLS-1$
.setAssociationName(fk.getName());
// Build AssosiationSet Ends
EdmAssociationSetEnd.Builder endOne = EdmAssociationSetEnd.newBuilder()
.setEntitySet(entitySet)
.setRoleName(table.getName())
.setRole(EdmAssociationEnd.newBuilder().setType(entityType).setRole(entityType.getName()));
EdmAssociationSetEnd.Builder endTwo = EdmAssociationSetEnd.newBuilder()
.setEntitySet(refEntitySet)
.setRoleName(fk.getReferenceTableName())
.setRole(EdmAssociationEnd.newBuilder().setType(refEntityType).setRole(refEntityType.getName()));
assosiationSet.setEnds(endOne, endTwo);
assosiationSet.setAssociation(association);
assosiationSets.add(assosiationSet);
}
}
entityContainer.addAssociationSets(assosiationSets);
odataSchema.addAssociations(assosiations);
}
private static String getNavigationName(
org.odata4j.edm.EdmEntityType.Builder entityType,
String tableName) {
String navigationName = tableName;
int i = 1;
while(true) {
for (EdmNavigationProperty.Builder b:entityType.getNavigationProperties()) {
if (b.getName().equals(navigationName)) {
navigationName = null;
break;
}
}
if (navigationName != null) {
return navigationName;
}
navigationName = tableName+(i++);
}
}
public static void buildFunctionImports(Schema schema, List<Builder> edmSchemas, boolean preserveEntityTypeName) {
EdmSchema.Builder odataSchema = findSchema(edmSchemas, schema.getName());
EdmEntityContainer.Builder entityContainer = findEntityContainer(edmSchemas, schema.getName());
// procedures
for(Procedure proc:schema.getProcedures().values()) {
EdmFunctionImport.Builder edmProcedure = EdmFunctionImport.newBuilder();
edmProcedure.setName(proc.getName());
String httpMethod = "POST"; //$NON-NLS-1$
for (ProcedureParameter pp:proc.getParameters()) {
if (pp.getName().equals("return")) { //$NON-NLS-1$
httpMethod = "GET"; //$NON-NLS-1$
edmProcedure.setReturnType(ODataTypeManager.odataType(pp.getRuntimeType()));
continue;
}
EdmFunctionParameter.Builder param = EdmFunctionParameter.newBuilder();
param.setName(pp.getName());
param.setType(ODataTypeManager.odataType(pp.getRuntimeType()));
if (pp.getType() == ProcedureParameter.Type.In) {
param.setMode(Mode.In);
}
else if (pp.getType() == ProcedureParameter.Type.InOut) {
param.setMode(Mode.InOut);
}
else if (pp.getType() == ProcedureParameter.Type.Out) {
param.setMode(Mode.Out);
}
param.setNullable(pp.getNullType() == NullType.Nullable);
edmProcedure.addParameters(param);
}
// add a complex type for return resultset.
ColumnSet<Procedure> returnColumns = proc.getResultSet();
if (returnColumns != null) {
httpMethod = "GET"; //$NON-NLS-1$
EdmComplexType.Builder complexType = EdmComplexType.newBuilder();
String entityTypeName = proc.getName()+"_"+returnColumns.getName(); //$NON-NLS-1$
if (preserveEntityTypeName && proc.getProperty(ODataMetadataProcessor.ENTITY_TYPE, false) != null) {
entityTypeName = proc.getProperty(ODataMetadataProcessor.ENTITY_TYPE, false);
}
complexType.setName(entityTypeName);
complexType.setNamespace(schema.getName());
for (Column c:returnColumns.getColumns()) {
EdmProperty.Builder property = EdmProperty.newBuilder(c.getName())
.setType(ODataTypeManager.odataType(c.getRuntimeType()))
.setNullable(c.getNullType() == NullType.Nullable);
if (c.getRuntimeType().equals(DataTypeManager.DefaultDataTypes.STRING)) {
property.setFixedLength(c.isFixedLength())
.setMaxLength(c.getLength())
.setUnicode(true);
}
complexType.addProperties(property);
}
odataSchema.addComplexTypes(complexType);
edmProcedure.setIsCollection(true);
edmProcedure.setReturnType(EdmCollectionType.newBuilder().setCollectionType(complexType).setKind(CollectionKind.Collection));
}
edmProcedure.setHttpMethod(httpMethod);
entityContainer.addFunctionImports(edmProcedure);
}
}
static List<String> getColumnNames(List<Column> columns){
ArrayList<String> names = new ArrayList<String>();
for (Column c: columns) {
names.add(c.getName());
}
return names;
}
static boolean sameColumnSet(KeyRecord recordOne, KeyRecord recordTwo) {
if (recordOne == null || recordTwo == null) {
return false;
}
List<Column> setOne = recordOne.getColumns();
List<Column> setTwo = recordTwo.getColumns();
if (setOne.size() != setTwo.size()) {
return false;
}
for (int i = 0; i < setOne.size(); i++) {
Column one = setOne.get(i);
Column two = setTwo.get(i);
if (!one.getName().equals(two.getName())) {
return false;
}
}
return true;
}
static EdmEntitySet removeModelName(EdmEntitySet src) {
EdmEntityType srcType = src.getType();
String schemaName = srcType.getName().substring(0, srcType.getName().indexOf('.'));
String name = srcType.getName().substring(srcType.getName().indexOf('.')+1);
EdmEntityType.Builder targetType = EdmEntityType
.newBuilder().setName(name)
.setNamespace(schemaName);
targetType.addKeys(srcType.getKeys());
Enumerable<EdmProperty> properties = srcType.getProperties();
for (EdmProperty srcProperty:properties.toList()) {
EdmProperty.Builder tgtProperty = EdmProperty.newBuilder(srcProperty.getName())
.setType(srcProperty.getType())
.setNullable(srcProperty.isNullable())
.setFixedLength(srcProperty.getFixedLength())
.setMaxLength(srcProperty.getMaxLength())
.setUnicode(true);
targetType.addProperties(tgtProperty);
}
EdmEntitySet.Builder target = EdmEntitySet.newBuilder()
.setName(src.getName())
.setEntityType(targetType);
return target.build();
}
}